Compare commits

..

47 Commits

Author SHA1 Message Date
afornerot 442718316d note sur les issues et report pv propal 2024-09-06 17:56:42 +02:00
afornerot 4c9b0a1db2 fix stat clos 2024-06-15 13:12:26 +02:00
afornerot 88ffe47f6e fix stat 2024-06-15 12:25:58 +02:00
afornerot 0d9caea067 fix dump 2024-06-15 11:04:50 +02:00
afornerot 9171ecbf6c fix ctrlchange 2024-06-15 11:02:32 +02:00
afornerot b155036347 optiomisation tbestim 2024-06-14 20:46:08 +02:00
afornerot 8bc58f6966 optimisation getissue gitea 2024-06-14 18:44:30 +02:00
afornerot a2d1841631 feat(#20): possibilité d'assigner des intervenants sur un ticket 2024-06-14 15:07:35 +02:00
afornerot 0249b6cfae feat(#20): possibilité d'assigner des intervenants sur un ticket 2024-06-12 16:09:40 +02:00
afornerot 31876489ca formattage tag 2024-05-07 17:24:15 +02:00
afornerot e2142caa49 del dump 2024-04-15 13:46:02 +02:00
afornerot d9448ccfee link unlink 2024-04-15 13:25:09 +02:00
afornerot 0b15ea51bd return block 2024-04-12 22:31:02 +02:00
afornerot b530a4d8ff color 2024-04-12 21:56:11 +02:00
afornerot d2ff60af5c correction largeur block 2024-04-12 20:30:48 +02:00
afornerot e98e1f2698 color priority 2024-04-12 20:27:25 +02:00
afornerot 8f8f53bff3 correction largeur block 2024-04-12 17:15:28 +02:00
afornerot 9dc93daab8 block / unblock && sprint closed 2024-04-12 16:30:36 +02:00
afornerot 0acb4e1777 ajout poid sur export csv 2024-04-11 15:14:49 +02:00
afornerot 0c51a19726 toggle sur conteneur sprint 2024-03-15 16:49:22 +01:00
afornerot 13c9c8bd3d websocket init 2024-03-15 15:44:29 +01:00
afornerot 11c6d4635f svg 2024-02-23 13:28:45 +01:00
afornerot cc200a9862 make update 2024-02-23 13:05:46 +01:00
afornerot b81226d125 detail issu by default 2024-02-23 13:04:21 +01:00
afornerot 072afb2a1c weight in scrum view 2024-02-23 12:35:29 +01:00
afornerot c61312bc3d stat by number of issue 2024-02-22 17:03:25 +01:00
afornerot 37968a3728 gestion suppression label statut 2024-02-22 14:54:09 +01:00
afornerot 0989e4762c gestion suppression label statut 2024-02-22 14:52:15 +01:00
afornerot ab2b1e6d3f estim sprint 2024-02-21 22:25:43 +01:00
afornerot 4f88121362 estim sprint 2024-02-21 22:18:01 +01:00
afornerot 2df92a3b7f detail issue 2024-02-21 20:58:38 +01:00
afornerot 3d949eefce resolution refresh 2024-02-20 13:28:51 +01:00
afornerot 54f9156149 resolution refresh 2024-02-20 13:28:20 +01:00
afornerot 94993f9a10 resolution refresh 2024-02-20 12:00:40 +01:00
afornerot dae554a45d export sprint in csv 2024-02-20 11:20:30 +01:00
afornerot 1c572dd516 export sprint in csv 2024-02-20 11:12:38 +01:00
afornerot 4c77a4bcd1 ano lastupdate 2024-02-20 11:01:03 +01:00
afornerot 88a518ec1c ano lastupdate 2024-02-20 10:59:26 +01:00
afornerot 86fcff316f submenu 2024-02-20 10:39:49 +01:00
afornerot 8cfee4699e force redirect to app_protocol 2024-02-20 10:22:06 +01:00
afornerot 652d3365a1 force redirect to app_protocol 2024-02-20 10:12:27 +01:00
afornerot 8e96aee7ab toggle view issue 2024-02-17 19:01:29 +01:00
afornerot 1666811787 condensed view 2024-02-16 16:40:41 +01:00
afornerot 1714bd9a70 prise en compte protocole 2024-02-16 13:53:54 +01:00
afornerot 70ea9bff97 refresh token 2024-02-15 22:36:10 +01:00
afornerot 9205735d1f mise en place de sprint dans les jalons 2024-02-15 18:59:09 +01:00
afornerot ecb1d43b38 resolution upload avatar 2024-02-13 11:57:50 +01:00
47 changed files with 3811 additions and 1056 deletions

5
.env
View File

@ -1,6 +1,7 @@
# Symfony # Symfony
APP_ENV=PROD APP_ENV=PROD
APP_SECRET=changemeinenvlocal APP_SECRET=changemeinenvlocal
APP_PROTOCOL=https
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 #TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$' #TRUSTED_HOSTS='^(localhost|example\.com)$'
@ -39,9 +40,9 @@ MAILER_NOREPLY=noreply@noreply.fr
# WEBSOCKET # WEBSOCKET
WSS_USE=0 WSS_USE=1
WSS_PORT=5588 WSS_PORT=5588
WSS_URL=ws://localhost:5588
# Proxy # Proxy
PROXY_USE=0 PROXY_USE=0

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ yarn-error.log
/public/uploads/logo/* /public/uploads/logo/*
!/public/uploads/logo/logo.png !/public/uploads/logo/logo.png
/public/uploads/ckeditor /public/uploads/ckeditor
/public/uploads/issues

3
Makefile Normal file
View File

@ -0,0 +1,3 @@
update:
git pull
docker-compose exec ninegitea /app/misc/script/reconfigure.sh

View File

@ -53,7 +53,11 @@
"preferred-install": { "preferred-install": {
"*": "dist" "*": "dist"
}, },
"sort-packages": true "sort-packages": true,
"allow-plugins": {
"ocramius/package-versions": true,
"symfony/flex": true
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -1,11 +0,0 @@
oneup_uploader:
mappings:
avatar:
frontend: dropzone
logo:
frontend: dropzone
document:
frontend: dropzone
namer: app.upload.samename
storage:
directory: "%kernel.project_dir%/uploads/document"

View File

@ -1,6 +1,6 @@
# Read the documentation: https://github.com/1up-lab/OneupUploaderBundle/blob/master/Resources/doc/index.md
oneup_uploader: oneup_uploader:
mappings: mappings:
# This is a mapping example, remove it and create your own mappings. avatar:
gallery: frontend: dropzone
frontend: dropzone # or any uploader you use in the frontend logo:
frontend: dropzone

View File

@ -69,7 +69,7 @@ app_sonde:
#== Wss ================================================================================================================== #== Wss ==================================================================================================================
app_wss_sample: app_wss_sample:
path: /user/wss/sampble path: /user/wss/sample
defaults: { _controller: App\Controller\WebsocketController:sample } defaults: { _controller: App\Controller\WebsocketController:sample }
#== Crop ================================================================================================================= #== Crop =================================================================================================================
@ -173,6 +173,10 @@ app_report_csv:
path: /user/report/csv/{id} path: /user/report/csv/{id}
defaults: { _controller: App\Controller\ReportController:csv } defaults: { _controller: App\Controller\ReportController:csv }
app_report_milestone:
path: /user/report/milestone/{idscrum}/{mode}/{month}
defaults: { _controller: App\Controller\ReportController:milestone }
app_report_test: app_report_test:
path: /user/report/test/{id} path: /user/report/test/{id}
defaults: { _controller: App\Controller\ReportController:test } defaults: { _controller: App\Controller\ReportController:test }
@ -182,22 +186,10 @@ app_scrum:
path: /user/scrum path: /user/scrum
defaults: { _controller: App\Controller\ScrumController:list } defaults: { _controller: App\Controller\ScrumController:list }
app_scrum_view:
path: /user/scrum/{id}
defaults: { _controller: App\Controller\ScrumController:view }
app_scrum_submit: app_scrum_submit:
path: /master/scrum/submit path: /master/scrum/submit
defaults: { _controller: App\Controller\ScrumController:submit } defaults: { _controller: App\Controller\ScrumController:submit }
app_scrum_stat:
path: /user/scrum/stat/{id}
defaults: { _controller: App\Controller\ScrumController:stat }
app_scrum_info:
path: /user/scrum/info/{id}
defaults: { _controller: App\Controller\ScrumController:info }
app_scrum_update: app_scrum_update:
path: /master/scrum/update/{id} path: /master/scrum/update/{id}
defaults: { _controller: App\Controller\ScrumController:update } defaults: { _controller: App\Controller\ScrumController:update }
@ -206,6 +198,25 @@ app_scrum_delete:
path: /master/scrum/delete/{id} path: /master/scrum/delete/{id}
defaults: { _controller: App\Controller\ScrumController:delete } defaults: { _controller: App\Controller\ScrumController:delete }
app_scrum_view:
path: /user/scrum/{id}
defaults: { _controller: App\Controller\ScrumController:view }
app_scrum_table:
path: /user/scrum/table/{id}
defaults: { _controller: App\Controller\ScrumController:table }
app_scrum_stat:
path: /user/scrum/stat/{id}
defaults: { _controller: App\Controller\ScrumController:stat }
app_scrum_link:
path: /user/scrum/link/{id}
defaults: { _controller: App\Controller\ScrumController:link }
app_scrum_info:
path: /user/scrum/info/{id}
defaults: { _controller: App\Controller\ScrumController:info }
#== Scrumcolumn ======================================================================================================== #== Scrumcolumn ========================================================================================================
app_scrumcolumn_submit: app_scrumcolumn_submit:
@ -228,7 +239,6 @@ app_scrumcolumn_order:
path: /master/scrumcolumn/order/{scrumid} path: /master/scrumcolumn/order/{scrumid}
defaults: { _controller: App\Controller\ScrumcolumnController:order } defaults: { _controller: App\Controller\ScrumcolumnController:order }
#== Scrumteam ======================================================================================================== #== Scrumteam ========================================================================================================
app_scrumteam_submit: app_scrumteam_submit:
path: /master/scrumteam/submit/{scrumid} path: /master/scrumteam/submit/{scrumid}
@ -250,7 +260,6 @@ app_scrumteam_order:
path: /master/scrumteam/order/{scrumid} path: /master/scrumteam/order/{scrumid}
defaults: { _controller: App\Controller\ScrumteamController:order } defaults: { _controller: App\Controller\ScrumteamController:order }
#== Scrumpriority ======================================================================================================== #== Scrumpriority ========================================================================================================
app_scrumpriority_submit: app_scrumpriority_submit:
path: /master/scrumpriority/submit/{scrumid} path: /master/scrumpriority/submit/{scrumid}
@ -272,6 +281,26 @@ app_scrumpriority_order:
path: /master/scrumpriority/order/{scrumid} path: /master/scrumpriority/order/{scrumid}
defaults: { _controller: App\Controller\ScrumpriorityController:order } defaults: { _controller: App\Controller\ScrumpriorityController:order }
#== Scrumsprint ========================================================================================================
app_scrumsprint_submit:
path: /master/scrumsprint/submit/{scrumid}
defaults: { _controller: App\Controller\ScrumsprintController:submit }
app_scrumsprint_update:
path: /master/scrumsprint/update/{id}
defaults: { _controller: App\Controller\ScrumsprintController:update }
app_scrumsprint_delete:
path: /master/scrumsprint/delete/{id}
defaults: { _controller: App\Controller\ScrumsprintController:delete }
app_scrumsprint_select:
path: /master/scrumsprint/select/{scrumid}
defaults: { _controller: App\Controller\ScrumsprintController:select }
app_scrumsprint_order:
path: /master/scrumsprint/order/{scrumid}
defaults: { _controller: App\Controller\ScrumsprintController:order }
#== Scrumtype ======================================================================================================== #== Scrumtype ========================================================================================================
app_scrumtype_submit: app_scrumtype_submit:
@ -311,15 +340,35 @@ app_scrumissue_update:
path: /user/scrumissue/update path: /user/scrumissue/update
defaults: { _controller: App\Controller\ScrumissueController:update } defaults: { _controller: App\Controller\ScrumissueController:update }
app_scrumissue_block:
path: /user/scrumissue/block
defaults: { _controller: App\Controller\ScrumissueController:block }
app_scrumissue_unblock:
path: /user/scrumissue/unblock
defaults: { _controller: App\Controller\ScrumissueController:unblock }
app_scrumissue_color:
path: /user/scrumissue/color
defaults: { _controller: App\Controller\ScrumissueController:color }
app_scrumissue_assigne:
path: /user/scrumissue/assigne
defaults: { _controller: App\Controller\ScrumissueController:assigne }
app_scrumissue_notes:
path: /user/scrumissue/notes
defaults: { _controller: App\Controller\ScrumissueController:notes }
app_scrumissue_ctrlchange: app_scrumissue_ctrlchange:
path: /user/scrumissue/ctrlchange path: /user/scrumissue/ctrlchange
defaults: { _controller: App\Controller\ScrumissueController:ctrlchange } defaults: { _controller: App\Controller\ScrumissueController:ctrlchange }
#== Issue ======================================================================================================== app_scrumissue_view:
app_issue: path: /user/scrumissue/view/{id}
path: /user/issue defaults: { _controller: App\Controller\ScrumissueController:view }
defaults: { _controller: App\Controller\IssueController:list, id: 0 }
app_issuescrum: #== Poker =============================================================================================================
path: /user/issuescrum/{id} app_poker_get:
defaults: { _controller: App\Controller\IssueController:list } path: /user/poker/{userid}/{issueid}
defaults: { _controller: App\Controller\ScrumissueController:getpoker }

View File

@ -6,6 +6,7 @@
parameters: parameters:
appEnv: '%env(resolve:APP_ENV)%' appEnv: '%env(resolve:APP_ENV)%'
appSecret: '%env(resolve:APP_SECRET)%' appSecret: '%env(resolve:APP_SECRET)%'
appProtocol: '%env(resolve:APP_PROTOCOL)%'
appWeburl: '%env(resolve:APP_WEBURL)%' appWeburl: '%env(resolve:APP_WEBURL)%'
appAuth: '%env(resolve:APP_AUTH)%' appAuth: '%env(resolve:APP_AUTH)%'
@ -33,7 +34,7 @@ parameters:
wssuse: '%env(resolve:WSS_USE)%' wssuse: '%env(resolve:WSS_USE)%'
wssport: '%env(resolve:WSS_PORT)%' wssport: '%env(resolve:WSS_PORT)%'
wssurl: 'wss://%env(resolve:APP_WEBURL)%/wss%env(resolve:APP_ALIAS)%' wssurl: '%env(resolve:WSS_URL)%'
proxyUse: '%env(resolve:PROXY_USE)%' proxyUse: '%env(resolve:PROXY_USE)%'
proxyHost: '%env(resolve:PROXY_HOST)%' proxyHost: '%env(resolve:PROXY_HOST)%'
@ -104,3 +105,8 @@ services:
public: true public: true
class: App\Service\giteaService class: App\Service\giteaService
arguments: ["@session","%giteaUrl%"] arguments: ["@session","%giteaUrl%"]
App\Websocket\MessageHandler:
public: true
arguments:
- "@service_container"

View File

@ -22,8 +22,24 @@ services:
container_name: ninegitea-app container_name: ninegitea-app
image: reg.cadoles.com/envole/ninegitea image: reg.cadoles.com/envole/ninegitea
ports: ports:
- "8000:80" - "8005:80"
- "5588:5588"
volumes:
- ./src:/app/src:delegated
- ./templates:/app/templates:delegated
- ./config:/app/config:delegated
- ./.env:/app/.env:delegated
- ./public/uploads:/app/public/uploads:delegated
- ./misc:/app/misc:delegated
adminer:
image: adminer
container_name: ninegitea-adminer
restart: always
ports:
- 6081:8080
volumes: volumes:
mariadb-data: mariadb-data:

View File

@ -12,4 +12,6 @@ composer install --no-interaction
bin/console d:s:u --force --complete bin/console d:s:u --force --complete
bin/console app:Websocket &
exec $@ exec $@

View File

@ -11,7 +11,10 @@ class HomeController extends AbstractController
{ {
public function home() public function home()
{ {
if($this->getUser())
return $this->redirectToRoute("app_scrum"); return $this->redirectToRoute("app_scrum");
else
return $this->redirectToRoute("app_login");
} }
public function admin() public function admin()

View File

@ -1,212 +0,0 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use App\Service\giteaService;
class IssueController extends AbstractController
{
private $appKernel;
private $data = "issue";
private $route = "app_issue";
private $render = "Issue/";
private $entity = "App:Issue";
public function __construct(KernelInterface $appKernel,giteaService $giteaservice) {
$this->appKernel = $appKernel;
$this->giteaservice = $giteaservice;
}
public function list($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$fgcsv = $request->get("fgcsv");
// Récupérer les repos de gitea
if($id==0) $scrums=$em->getRepository("App:Scrum")->findBy([],["name"=>"ASC"]);
else $scrums=$em->getRepository("App:Scrum")->findBy(["id"=>$id],["name"=>"ASC"]);
$giteacategorys=[];
$gitearepos=[];
$giteamilestones=[];
$giteacolumns=[];
$giteateams=[];
$giteaprioritys=[];
$giteatypes=[];
$gitealabels=[];
$giteaassignees=$em->getRepository("App:User")->findBy([],["username"=>"ASC"]);
$viewclosed = $request->getSession()->get("viewclosed");
foreach($scrums as $key => $scrum) {
if(!in_array($scrum->getCategory(),$giteacategorys))
array_push($giteacategorys,$scrum->getCategory());
$gitearepo=$this->giteaservice->getRepo($scrum->getGiteajson()["id"]);
if(!$gitearepo) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>");
if($gitearepo->updated_at!=$scrum->getGiteajson()["updated_at"]||$gitearepo->open_issues_count!=$scrum->getGiteajson()["open_issues_count"]) {
$scrum->setGiteajson(json_decode(json_encode($gitearepo), true));
$em->persist($scrum);
$em->flush();
}
if($gitearepo->open_issues_count>0) {
$giteatmp=$this->giteaservice->getMilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open"));
foreach($giteatmp as $key => $value) {
$giteatmp[$key]->title = $scrum->getGiteajson()["full_name"]. " = ".$giteatmp[$key]->title;
}
array_push($giteatmp,["id"=>$scrum->getGiteajson()["full_name"],"title"=> $scrum->getGiteajson()["full_name"]. " = Aucun Jalon"]);
$giteamilestones=array_merge($giteamilestones,$giteatmp);
$giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open"));
$json=$scrum->getGiteajson();
$json["issues"]=$giteaissues;
$json["category"]=$scrum->getCategory();
foreach($json["issues"] as $key => $giteaissue) {
$issue=$em->getRepository("App:Scrumissue")->findOneBy(["giteaid"=>$giteaissue->id]);
if($issue) {
$json["issues"][$key]->weight=$issue->getWeight();
$json["issues"][$key]->issueid=$issue->getId();
$json["issues"][$key]->scrumid=$issue->getScrum()->getId();
}
else {
$json["issues"][$key]->weight=0;
$json["issues"][$key]->issueid=0;
$json["issues"][$key]->scrumid=0;
}
}
$tmp=[];
foreach($scrum->getScrumcolumns() as $column) {
array_push($tmp,$column->getGiteaid());
if(!in_array($column->getGiteajson()["name"],$giteacolumns))
array_push($giteacolumns,$column->getGiteajson()["name"]);
}
$json["columns"]=$tmp;
array_push($gitearepos,$json);
foreach($scrum->getScrumteams() as $team) {
if(!in_array($team->getGiteajson()["name"],$giteateams))
array_push($giteateams,$team->getGiteajson()["name"]);
}
foreach($scrum->getScrumprioritys() as $priority) {
if(!in_array($priority->getGiteajson()["name"],$giteaprioritys))
array_push($giteaprioritys,$priority->getGiteajson()["name"]);
}
foreach($scrum->getScrumtypes() as $type) {
if(!in_array($type->getGiteajson()["name"],$giteatypes))
array_push($giteatypes,$type->getGiteajson()["name"]);
}
foreach($giteaissues as $giteaissue) {
foreach($giteaissue->labels as $label) {
if(!in_array($label->name,$gitealabels))
array_push($gitealabels,$label->name);
}
}
}
}
$keysort = array_column($giteamilestones, 'title');
array_multisort($keysort, SORT_DESC, $giteamilestones);
sort($giteacolumns);
sort($giteateams);
sort($giteaprioritys);
sort($giteatypes);
sort($gitealabels);
sort($giteacategorys);
// Préférences utilisateur
$filtercategorys = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtercategorys",$id);
$filterrepos = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterrepos",$id);
$filtermilestones = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtermilestones",$id);
$filtercolumns = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtercolumns",$id);
$filterteams = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterteams",$id);
$filterprioritys = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterprioritys",$id);
$filtertypes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtertypes",$id);
$filterlabels = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterlabels",$id);
$filterassignees = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterassignees",$id);
$filterexcludes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterexcludes",$id);
$showfilters = $em->getRepository("App:User")->getUserpreference($this->getUser(),"showfilters",$id);
if($fgcsv) {
$dir = $this->appKernel->getProjectDir() . '/uploads/issues/';
$file = "issues-".$id.".csv";
$fs = new Filesystem();
$fs->mkdir($dir);
$csvh = fopen($dir.$file, 'w');
$d = ';'; // this is the default but i like to be explicit
$e = '"'; // this is the default but i like to be explicit
$tmp=["Projet","Jalon","Type","Id","Titre","Statut","Label"];
fputcsv($csvh, $tmp, $d, $e);
foreach($gitearepos as $gitearepo) {
foreach($gitearepo["issues"] as $giteaissue) {
$statut="";
$type="";
$labels="";
foreach($giteaissue->labels as $label) {
if(in_array($label->id,$gitearepo["columns"]))
$statut=$label->name;
elseif(in_array($label->name,$giteatypes))
$type=$label->name;
else
$labels=$labels.($labels!=""?"\n":"").$label->name;
}
$tmp=[
$gitearepo["name"],
(isset($giteaissue->milestone->title)?$giteaissue->milestone->title:""),
$type,
$giteaissue->number,
$giteaissue->title,
$statut,
$labels
];
fputcsv($csvh, $tmp, $d, $e);
}
}
fclose($csvh);
$response = new BinaryFileResponse($dir.$file);
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
return $response;
}
return $this->render($this->render.'list.html.twig',[
"useheader" => true,
"usesidebar" => false,
"id" => $id,
"giteacategorys" => $giteacategorys,
"gitearepos" => $gitearepos,
"giteamilestones" => $giteamilestones,
"giteacolumns" => $giteacolumns,
"giteateams" => $giteateams,
"giteaprioritys" => $giteaprioritys,
"giteatypes" => $giteatypes,
"gitealabels" => $gitealabels,
"giteaassignees" => $giteaassignees,
"filtercategorys" => $filtercategorys,
"filterrepos" => $filterrepos,
"filtermilestones" => $filtermilestones,
"filtercolumns" => $filtercolumns,
"filterteams" => $filterteams,
"filterprioritys" => $filterprioritys,
"filtertypes" => $filtertypes,
"filterlabels" => $filterlabels,
"filterexcludes" => $filterexcludes,
"filterassignees" => $filterassignees,
"showfilters" => $showfilters,
]);
}
}

View File

@ -13,6 +13,9 @@ use App\Service\giteaService;
class ReportController extends AbstractController class ReportController extends AbstractController
{ {
private KernelInterface $appKernel;
private giteaService $giteaservice;
public function __construct(KernelInterface $appKernel, giteaService $giteaservice) { public function __construct(KernelInterface $appKernel, giteaService $giteaservice) {
$this->appKernel = $appKernel; $this->appKernel = $appKernel;
$this->giteaservice = $giteaservice; $this->giteaservice = $giteaservice;
@ -114,6 +117,80 @@ class ReportController extends AbstractController
} }
public function milestone($idscrum,$mode,$month,Request $request) {
$em = $this->getDoctrine()->getManager();
$scrum = $em->getRepository("App:Scrum")->find($idscrum);
$datestart=new \DateTime($month."01");
$dateend=new \DateTime($month."31");
$repoid = $scrum->getGiteaid();
$repoowner = $scrum->getGiteajson()["owner"]["login"];
$reponame = $scrum->getGiteajson()["name"];
//$scrumissues = $em->getRepository("App:Scrumissue")->findBy(["scrum"=>$scrum,"giteastate"=>"open"],["giteamilestonename"=>"ASC","rowid"=>"DESC"]);
$scrumissues = $em->getRepository("App:Scrumissue")->findBy(["scrum"=>$scrum],["giteamilestonename"=>"ASC","rowid"=>"DESC"]);
$reportissues=[];
foreach($scrumissues as $scrumissue) {
$labels=array_column($scrumissue->getGiteajson()["labels"], 'name');
$update=new \DateTime($scrumissue->getGiteajson()["updated_at"]);
$close=new \DateTime($scrumissue->getGiteajson()["closed_at"]);
//"created_at" => "2024-05-03T15:09:26+02:00"
//"updated_at" => "2024-05-03T16:02:00+02:00"
//"closed_at" => "2024-05-03T16:01:59+02:00"
//if(in_array("Type/Scénario",$labels)&&in_array("Statut/Backlog",$labels)) {
//if(in_array("Type/Scénario",$labels)) {
// Dans le cas d'un report Propal on ne prend que les tickets ouvert de type scénario du backlog
$ok=false;
if($mode=="Propal") {
if(in_array("Type/Scénario",$labels)&&in_array("Statut/Backlog",$labels)&&$scrumissue->getGiteastate()=="open") {
$ok=true;
}
}
// Dans le cas d'un report PV on prend tout les tickets avec une date de modification ou de cloture dans le mois
if($mode=="PV") {
$isstatut=false;
$isupdate=false;
$isclose=false;
if(in_array("Statut/Livré PREPROD",$labels) || in_array("Statut/A Livrer PROD",$labels) || in_array("Statut/Livré PROD",$labels)) {
$isstatut=true;
}
if(!empty($close)&&$close>=$datestart&&$close<=$dateend) $isclose=true;
if($update>=$datestart&&$update<=$dateend) $isupdate=true;
if($isstatut&&($isclose||$isupdate)) $ok=true;
}
if(!$ok) continue;
$tmp= [
"id" => $scrumissue->getGiteanumber(),
"title" => $scrumissue->getGiteatitle(),
"milestone" => ($scrumissue->getGiteamilestonename()??"Aucun"),
"statutorder" => $scrumissue->getScrumcolumn()->getRowid(),
"statut" => $scrumissue->getScrumcolumn()->getName(),
"issueorder" => $scrumissue->getRowid(),
];
array_push($reportissues,$tmp);
}
$statutsort = array_column($reportissues,"statutorder");
$issuesort = array_column($reportissues,"statutorder");
$milestonesort = array_column($reportissues,"milestone");
array_multisort($statutsort, SORT_ASC, $milestonesort, SORT_ASC, $issuesort, SORT_ASC, $reportissues);
return $this->render('Report/milestone.html.twig', [
'useheader' => true,
'issues' => $reportissues,
]);
}
public function test($id,Request $request) public function test($id,Request $request)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();

View File

@ -4,9 +4,10 @@ namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Form\FormError; use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use App\Entity\Scrum as Entity; use App\Entity\Scrum as Entity;
use App\Entity\Scrumissue as Scrumissue; use App\Entity\Scrumissue as Scrumissue;
@ -20,6 +21,7 @@ class ScrumController extends AbstractController
private $route = "app_scrum"; private $route = "app_scrum";
private $render = "Scrum/"; private $render = "Scrum/";
private $entity = "App:Scrum"; private $entity = "App:Scrum";
private $giteaservice;
public function __construct(giteaService $giteaservice) { $this->giteaservice = $giteaservice; } public function __construct(giteaService $giteaservice) { $this->giteaservice = $giteaservice; }
@ -28,6 +30,8 @@ class ScrumController extends AbstractController
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$scrums = $em->getRepository($this->entity)->findByUser($this->getUser()); $scrums = $em->getRepository($this->entity)->findByUser($this->getUser());
$this->giteaservice->needrefresh();
$giteacategorys=[]; $giteacategorys=[];
$gitearepos=[]; $gitearepos=[];
@ -175,14 +179,223 @@ class ScrumController extends AbstractController
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$data=$em->getRepository($this->entity)->find($id); $data=$em->getRepository($this->entity)->find($id);
if(!$data) return $this->redirectToRoute($this->route); if(!$data) return $this->redirectToRoute($this->route);
$firstcolumn=$em->getRepository('App:Scrumcolumn')->findOneBy(["scrum"=>$data], ['rowid' => 'ASC']);
if(!$firstcolumn) return $this->redirectToRoute($this->route);
$forcereload=false; $forcereload=false;
if($request->get("forcereload")) $forcereload=$request->get("forcereload"); if($request->get("forcereload")) $forcereload=$request->get("forcereload");
// Mise à jour des issues par rapport à gitea
$em->getRepository("App:Scrum")->getGitea($data,$giteaassignees,$giteacolumns,$giteamilestones,$giteateams,$giteaprioritys,$giteatypes,$gitealabels,$forcereload); $em->getRepository("App:Scrum")->getGitea($data,$giteaassignees,$giteacolumns,$giteamilestones,$giteateams,$giteaprioritys,$giteatypes,$gitealabels,$forcereload);
// default color priority
$prioritycolor="#70c24a";
if(is_array($giteaprioritys)) {
$priority=$em->getRepository("App:Scrumpriority")->findOneBy(["scrum"=>$data,"giteaid"=>$giteaprioritys[array_key_last($giteaprioritys)]]);
if($priority) {
$prioritycolor="#".$priority->getGiteajson()["color"];
}
}
// Création du tableau des issues
$issues=$data->getScrumissues();
$tbissues=[];
$tbcols=[];
$tbjals=[];
$tbsprs=[];
$tbestim=[];
$viewclosed = $this->get('session')->get("viewclosed");
foreach($issues as $issue) {
// bypass closed
if($viewclosed=="false"&&($issue->getGiteastate()=="closed"||($issue->getScrumsprint()&&$issue->getScrumsprint()->getClosed()))) continue;
// Ids
$idcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getId():$firstcolumn->getId());
$idjal=($issue->getGiteamilestone()?$issue->getGiteamilestone():-100);
$idspr=($issue->getScrumsprint()?$issue->getScrumsprint()->getId():-100);
// Roworders
$rowcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getRowid():$firstcolumn->getRowid());
$rowjal=($issue->getGiteaMilestonename()?$issue->getGiteaMilestonename():-100);
$rowspr=($issue->getScrumsprint()?$issue->getScrumsprint()->getRowid():-100);
// Names
$nmcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getName():$firstcolumn->getName());
$nmjal=($issue->getGiteaMilestonename()?$issue->getGiteaMilestonename():"Aucun");
$nmspr=($issue->getScrumsprint()?$issue->getScrumsprint()->getName():"Aucun");
// Idgiteas
$gicol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getGiteaid():$firstcolumn->getGiteaid());
$gijal=($issue->getGiteaMilestone()?$issue->getGiteaMilestone():-100);
// Liste des colonnes/jalons/sprint avec des issues
if(!in_array($idcol,$tbcols)) array_push($tbcols,$idcol);
if(!in_array($idcol."|".$idjal,$tbjals)) array_push($tbjals,$idcol."|".$idjal);
if(!in_array($idcol."|".$idjal."|".$idspr,$tbsprs)) array_push($tbsprs,$idcol."|".$idjal."|".$idspr);
// Initialisation du tableau des estimations
if(!array_key_exists($idjal,$tbestim)) {
$tbestim[$idjal] = [
"rowjal" => $rowjal,
"idjal" => $idjal,
"nmjal" => $nmjal,
"gijal" => $gijal,
"nbjrs" => 0,
"sprints" => []
];
}
if(!array_key_exists($idspr,$tbestim[$idjal]["sprints"])) {
$tbestim[$idjal]["sprints"][$idspr] = [
"rowspr" => $rowspr,
"idspr" => $idspr,
"nmspr" => $nmspr,
"nbjrs" => 0,
];
}
// Initialisation du tableau des colonnes
if(!array_key_exists($idcol,$tbissues)) {
$tbissues[$idcol]=[
"rowcol" => $rowcol,
"idcol" => $idcol,
"nmcol" => $nmcol,
"gicol" => $gicol,
"nbjrs" => 0,
"jalons" => [],
];
}
// Initialisation du tableau des jalons de la colonne encours
if(!array_key_exists($idjal,$tbissues[$idcol]["jalons"])) {
$tbissues[$idcol]["jalons"][$idjal] = [
"rowjal" => $rowjal,
"idjal" => $idjal,
"nmjal" => $nmjal,
"gijal" => $gijal,
"nbjrs" => 0,
"sprints" => [],
];
}
// Initialisation du tableau des sprint de la colonne/jalon encours
if(!array_key_exists($idspr,$tbissues[$idcol]["jalons"][$idjal]["sprints"])) {
$tbissues[$idcol]["jalons"][$idjal]["sprints"][$idspr] = [
"rowspr" => $rowspr,
"idspr" => $idspr,
"nmspr" => $nmspr,
"nbjrs" => 0,
"issues" => [],
];
}
// On cumule les estimations
$tbissues[$idcol]["nbjrs"]+=$issue->getWeight();
$tbissues[$idcol]["jalons"][$idjal]["nbjrs"]+=$issue->getWeight();
$tbissues[$idcol]["jalons"][$idjal]["sprints"][$idspr]["nbjrs"]+=$issue->getWeight();
$tbestim[$idjal]["nbjrs"]+=$issue->getWeight();
$tbestim[$idjal]["sprints"][$idspr]["nbjrs"]+=$issue->getWeight();
// On sauvegarde l'issue
array_push($tbissues[$idcol]["jalons"][$idjal]["sprints"][$idspr]["issues"],$issue);
}
// On ajoute les colonnes sans issues
$columns=$data->getScrumcolumns();
foreach($columns as $column) {
if(!in_array($column->getId(),$tbcols)) {
$tbissues[$column->getId()] = [
"rowcol" => $column->getRowid(),
"idcol" => $column->getId(),
"nmcol" => $column->getName(),
"gicol" => $column->getGiteaid(),
"nbjrs" => 0,
"jalons" => [],
];
}
// On ajoute les jalons sans issues
foreach($giteamilestones as $milestone) {
if(!in_array($column->getId()."|".$milestone->id,$tbjals)) {
$tbissues[$column->getId()]["jalons"][$milestone->id] = [
"rowjal" => $milestone->title,
"idjal" => $milestone->id,
"nmjal" => $milestone->title,
"gijal" => $milestone->id,
"nbjrs" => 0,
"sprints" => [],
];
}
}
// On ajoute le jalon aucun si sans issue
if(!in_array($column->getId()."|-100",$tbjals)) {
$tbissues[$column->getId()]["jalons"][-100] = [
"rowjal" => -100,
"idjal" => -100,
"nmjal" => "Aucun",
"gijal" => null,
"nbjrs" => 0,
"sprints" => [],
];
}
// On ajoutes les sprints sans issues
foreach($tbissues[$column->getId()]["jalons"] as $jalon) {
$sprints=$em->getRepository("App:Scrumsprint")->findBy(["scrum"=>$data,"giteamilestone"=>$jalon["idjal"]]);
foreach($sprints as $sprint) {
if($viewclosed=="false"&&$sprint->getClosed()) continue;
if(!in_array($column->getId()."|".$jalon["idjal"]."|".$sprint->getId(),$tbsprs)) {
$tbissues[$column->getId()]["jalons"][$jalon["idjal"]]["sprints"][$sprint->getId()] = [
"rowspr" => $sprint->getRowid(),
"idspr" => $sprint->getId(),
"nmspr" => $sprint->getName(),
"nbjrs" => 0,
"issues" => [],
];
}
}
// On ajoute les sprint aucun sans issue
if(!in_array($column->getId()."|".$jalon["idjal"]."|-100",$tbsprs)) {
$tbissues[$column->getId()]["jalons"][$jalon["idjal"]]["sprints"][-100] = [
"rowspr" => -100,
"idspr" => -100,
"nmspr" => "Aucun",
"nbjrs" => 0,
"issues" => [],
];
}
}
}
// Tri des issues par colonne/jalon/sprint/issue
$rowcol = array_column($tbissues, 'rowcol');
array_multisort($rowcol, SORT_ASC, $tbissues);
foreach($tbissues as $keycol=>$column) {
$rowjal = array_column($tbissues[$keycol]["jalons"], 'rowjal');
array_multisort($rowjal, SORT_DESC, $tbissues[$keycol]["jalons"]);
foreach($tbissues[$keycol]["jalons"] as $keyjal=>$jalon) {
$rowspr = array_column($tbissues[$keycol]["jalons"][$keyjal]["sprints"], 'rowspr');
array_multisort($rowspr, SORT_DESC, $tbissues[$keycol]["jalons"][$keyjal]["sprints"]);
}
}
$rowjal = array_column($tbestim, 'rowjal');
array_multisort($rowjal, SORT_DESC, $tbestim);
foreach($tbestim as $keyjal=>$jalon) {
$rowspr = array_column($tbestim[$keyjal]["sprints"], 'rowspr');
array_multisort($rowspr, SORT_DESC, $tbestim[$keyjal]["sprints"]);
}
// Préférences utilisateur // Préférences utilisateur
$viewcondensed = $em->getRepository("App:User")->getUserpreference($this->getUser(),"viewcondensed",$id);
$filtermilestones = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtermilestones",$id); $filtermilestones = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtermilestones",$id);
$filtersprints = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtersprints",$id);
$filterteams = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterteams",$id); $filterteams = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterteams",$id);
$filterprioritys = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterprioritys",$id); $filterprioritys = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterprioritys",$id);
$filtertypes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtertypes",$id); $filtertypes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtertypes",$id);
@ -191,6 +404,11 @@ class ScrumController extends AbstractController
$filterexcludes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterexcludes",$id); $filterexcludes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterexcludes",$id);
$showfilters = $em->getRepository("App:User")->getUserpreference($this->getUser(),"showfilters",$id); $showfilters = $em->getRepository("App:User")->getUserpreference($this->getUser(),"showfilters",$id);
// Me demandez pas pourquoi mais j'ai un décallage de 30mn
$updatedate=$data->getUpdatedate();
$updatedate->setTimezone(new \DateTimeZone("UTC"));
$updatedate->add(new \DateInterval("PT30M"));
return $this->render($this->render.'view.html.twig', [ return $this->render($this->render.'view.html.twig', [
'useheader' => true, 'useheader' => true,
'usesidebar' => false, 'usesidebar' => false,
@ -198,11 +416,15 @@ class ScrumController extends AbstractController
'giteaassignees' => $giteaassignees, 'giteaassignees' => $giteaassignees,
'giteacolumns' => $giteacolumns, 'giteacolumns' => $giteacolumns,
'giteamilestones' => $giteamilestones, 'giteamilestones' => $giteamilestones,
'sprints' => $data->getScrumsprintstosee($viewclosed),
'giteateams' => $giteateams, 'giteateams' => $giteateams,
'giteaprioritys' => $giteaprioritys, 'giteaprioritys' => $giteaprioritys,
'prioritycolor' => $prioritycolor,
'giteatypes' => $giteatypes, 'giteatypes' => $giteatypes,
'gitealabels' => $gitealabels, 'gitealabels' => $gitealabels,
'viewcondensed' => $viewcondensed,
'filtermilestones' => $filtermilestones, 'filtermilestones' => $filtermilestones,
'filtersprints' => $filtersprints,
'filterteams' => $filterteams, 'filterteams' => $filterteams,
'filterprioritys' => $filterprioritys, 'filterprioritys' => $filterprioritys,
'filtertypes' => $filtertypes, 'filtertypes' => $filtertypes,
@ -210,141 +432,418 @@ class ScrumController extends AbstractController
'filterassignees' => $filterassignees, 'filterassignees' => $filterassignees,
'filterexcludes' => $filterexcludes, 'filterexcludes' => $filterexcludes,
'showfilters' => $showfilters, 'showfilters' => $showfilters,
'tbissues' => $tbissues,
'tbestim' => $tbestim,
'updatedate' => $updatedate,
$this->data => $data, $this->data => $data,
]); ]);
} }
public function table($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$fgcsv = $request->get("fgcsv");
$fgpoker = $request->get("fgpoker");
// Récupérer les repos de gitea
$scrum=$em->getRepository("App:Scrum")->findOneBy(["id"=>$id]);
$gitearepos=[];
$giteamilestones=[];
$giteacolumns=[];
$giteateams=[];
$giteaprioritys=[];
$giteatypes=[];
$gitealabels=[];
$giteaassignees=$em->getRepository("App:User")->findBy([],["username"=>"ASC"]);
$viewclosed = $request->getSession()->get("viewclosed");
$gitearepo=$this->giteaservice->getRepo($scrum->getGiteajson()["id"]);
if($gitearepo->updated_at!=$scrum->getGiteajson()["updated_at"]||$gitearepo->open_issues_count!=$scrum->getGiteajson()["open_issues_count"]) {
$scrum->setGiteajson(json_decode(json_encode($gitearepo), true));
$em->persist($scrum);
$em->flush();
}
$giteamilestones=$this->giteaservice->getMilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open"));
$giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open"));
$json=$scrum->getGiteajson();
$json["issues"]=$giteaissues;
foreach($json["issues"] as $key => $giteaissue) {
$issue=$em->getRepository("App:Scrumissue")->findOneBy(["giteaid"=>$giteaissue->id]);
if($issue) {
$json["issues"][$key]->weight=$issue->getWeight();
$json["issues"][$key]->issueid=$issue->getId();
$json["issues"][$key]->scrumid=$issue->getScrum()->getId();
$json["issues"][$key]->sprintid=($issue->getScrumsprint()?$issue->getScrumsprint()->getId():-100);
$json["issues"][$key]->sprintname=($issue->getScrumsprint()?$issue->getScrumsprint()->getName():"Aucun");
}
else {
$json["issues"][$key]->weight=0;
$json["issues"][$key]->issueid=0;
$json["issues"][$key]->scrumid=0;
$json["issues"][$key]->sprintid=-100;
$json["issues"][$key]->sprintname="Aucun";
}
}
$tmp=[];
foreach($scrum->getScrumcolumns() as $column) {
array_push($tmp,$column->getGiteaid());
if(!in_array($column->getGiteajson()["name"],$giteacolumns))
array_push($giteacolumns,$column->getGiteajson()["name"]);
}
$json["columns"]=$tmp;
array_push($gitearepos,$json);
foreach($scrum->getScrumteams() as $team) {
if(!in_array($team->getGiteajson()["name"],$giteateams))
array_push($giteateams,$team->getGiteajson()["name"]);
}
foreach($scrum->getScrumprioritys() as $priority) {
if(!in_array($priority->getGiteajson()["name"],$giteaprioritys))
array_push($giteaprioritys,$priority->getGiteajson()["name"]);
}
foreach($scrum->getScrumtypes() as $type) {
if(!in_array($type->getGiteajson()["name"],$giteatypes))
array_push($giteatypes,$type->getGiteajson()["name"]);
}
foreach($giteaissues as $giteaissue) {
foreach($giteaissue->labels as $label) {
if(!in_array($label->name,$gitealabels))
array_push($gitealabels,$label->name);
}
}
$keysort = array_column($giteamilestones, 'title');
array_multisort($keysort, SORT_DESC, $giteamilestones);
sort($giteacolumns);
sort($giteateams);
sort($giteaprioritys);
sort($giteatypes);
sort($gitealabels);
// Préférences utilisateur
$filtercategorys = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtercategorys",$id);
$filterrepos = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterrepos",$id);
$filtermilestones = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtermilestones",$id);
$filtersprints = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtersprints",$id);
$filtercolumns = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtercolumns",$id);
$filterteams = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterteams",$id);
$filterprioritys = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterprioritys",$id);
$filtertypes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtertypes",$id);
$filterlabels = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterlabels",$id);
$filterassignees = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterassignees",$id);
$filterexcludes = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterexcludes",$id);
$showfilters = $em->getRepository("App:User")->getUserpreference($this->getUser(),"showfilters",$id);
if($fgcsv) {
$dir = $this->getParameter('kernel.project_dir') . '/public/uploads/issues/';
$file = "issues-".$id.".csv";
$fs = new Filesystem();
$fs->mkdir($dir);
$csvh = fopen($dir.$file, 'w');
$d = ';'; // this is the default but i like to be explicit
$e = '"'; // this is the default but i like to be explicit
$tmp=["Projet","Jalon","Sprint","Type","Id","Titre","Statut","Label","Poid"];
fputcsv($csvh, $tmp, $d, $e);
foreach($gitearepos as $gitearepo) {
foreach($gitearepo["issues"] as $giteaissue) {
$statut="";
$issue=$em->getRepository("App:Scrumissue")->findOneBy(["giteaid"=>$giteaissue->id]);
$type="";
$labels="";
foreach($giteaissue->labels as $label) {
if(in_array($label->id,$gitearepo["columns"]))
$statut=$label->name;
elseif(in_array($label->name,$giteatypes))
$type=$label->name;
else
$labels=$labels.($labels!=""?"\n":"").$label->name;
}
$tmp=[
$gitearepo["name"],
(isset($giteaissue->milestone->title)?$giteaissue->milestone->title:""),
($issue->getScrumsprint()?$issue->getScrumsprint()->getName():"Aucun"),
$type,
$giteaissue->number,
$giteaissue->title,
$statut,
$labels,
$issue->getWeight()
];
fputcsv($csvh, $tmp, $d, $e);
}
}
fclose($csvh);
$response = new BinaryFileResponse($dir.$file);
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
return $response;
}
return $this->render($this->render.'table.html.twig',[
"useheader" => true,
"usesidebar" => false,
"id" => $id,
"gitearepos" => $gitearepos,
"giteamilestones" => $giteamilestones,
"sprints" => $scrum->getScrumsprints(),
"giteacolumns" => $giteacolumns,
"giteateams" => $giteateams,
"giteaprioritys" => $giteaprioritys,
"giteatypes" => $giteatypes,
"gitealabels" => $gitealabels,
"giteaassignees" => $giteaassignees,
"filtercategorys" => $filtercategorys,
"filterrepos" => $filterrepos,
"filtermilestones" => $filtermilestones,
"filtersprints" => $filtersprints,
"filtercolumns" => $filtercolumns,
"filterteams" => $filterteams,
"filterprioritys" => $filterprioritys,
"filtertypes" => $filtertypes,
"filterlabels" => $filterlabels,
"filterexcludes" => $filterexcludes,
"filterassignees" => $filterassignees,
"showfilters" => $showfilters,
"fgpoker" => $fgpoker,
]);
}
public function stat($id,Request $request) public function stat($id,Request $request)
{
$em = $this->getDoctrine()->getManager();
$scrum=$em->getRepository($this->entity)->find($id);
if(!$scrum) return $this->redirectToRoute($this->route);
$em->getRepository("App:Scrum")->getGitea($scrum,$giteaassignees,$giteacolumns,$giteamilestones,$giteateams,$giteaprioritys,$giteatypes,$gitealabels);
// Préférences utilisateur
$filtermilestones = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtermilestones",$id);
$filtersprints = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtersprints",$id);
$filterbynumber = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterbynumber",$id);
$showfilters = $em->getRepository("App:User")->getUserpreference($this->getUser(),"showfilters",$id);
$tbestim=$this->getEstim($scrum);
return $this->render($this->render.'stat.html.twig', [
'useheader' => true,
'usesidebar' => false,
'usetitle' => $scrum->getName(),
'giteamilestones' => $giteamilestones,
'sprints' => $scrum->getScrumsprints(),
'filtermilestones' => $filtermilestones,
'filtersprints' => $filtersprints,
'filterbynumber' => $filterbynumber,
'showfilters' => $showfilters,
$this->data => $scrum,
'tbestim' => $tbestim,
]);
}
public function link($id,Request $request)
{ {
// Initialisation de l'enregistrement // Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$data=$em->getRepository($this->entity)->find($id); $data=$em->getRepository($this->entity)->find($id);
if(!$data) return $this->redirectToRoute($this->route); if(!$data) return $this->redirectToRoute($this->route);
$em->getRepository("App:Scrum")->getGitea($data,$giteaassignees,$giteacolumns,$giteamilestones,$giteateams,$giteaprioritys,$giteatypes,$gitealabels); return $this->render($this->render.'link.html.twig',[
"useheader" => true,
// Préférences utilisateur "usesidebar" => false,
$filtermilestones = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filtermilestones",$id); "maxwidth" => 1000,
$filterteams = $em->getRepository("App:User")->getUserpreference($this->getUser(),"filterteams",$id); "scrum" => $data,
$showfilters = $em->getRepository("App:User")->getUserpreference($this->getUser(),"showfilters",$id);
$tbstat=[];
foreach($data->getScrumIssues() as $issue) {
$labels=$issue->getGiteajson()["labels"];
$haveteams=true;
if($filterteams) {
$haveteams=false;
foreach($filterteams as $filterteam) {
foreach($labels as $label) {
if($label["id"]==$filterteam) {
$haveteams=true;
}
}
}
}
if($haveteams) {
$idmilestone=($issue->getGiteamilestone()?$issue->getGiteamilestone():-100);
$lbmilestone=($issue->getGiteamilestone()?$issue->getGiteamilestonename():"Aucun");
if(!array_key_exists($idmilestone,$tbstat)) {
$tbstat[$idmilestone]=["id"=>$idmilestone,"name"=>$lbmilestone,"stat"=>[]];
}
if(!$issue->getScrumcolumn()) $ordercolumns=0;
else $ordercolumns=array_search($issue->getScrumcolumn()->getGiteaid(),$giteacolumns);
//$ordercolumns=$issue->getScrumcolumn()->getId();
if(!array_key_exists($ordercolumns,$tbstat[$idmilestone]["stat"])) {
$tbstat[$idmilestone]["stat"][$ordercolumns]=[
"id"=>$issue->getScrumcolumn()->getId(),
"label"=>$issue->getScrumcolumn()->getName(),
"total"=>0,
"color"=>"#".$issue->getScrumcolumn()->getGiteajson()["color"],
"labels"=>[],
];
}
foreach($labels as $label) {
if($ordercolumns!=$label["id"]) {
if(!array_key_exists($label["id"],$tbstat[$idmilestone]["stat"][$ordercolumns]["labels"])) {
$tbstat[$idmilestone]["stat"][$ordercolumns]["labels"][$label["id"]] = [
"id"=>$label["id"],
"label"=>$label["name"],
"total"=>0,
"color"=>"#".$label["color"],
];
}
$tbstat[$idmilestone]["stat"][$ordercolumns]["labels"][$label["id"]]["total"]+=$issue->getWeight();
}
}
$tbstat[$idmilestone]["stat"][$ordercolumns]["total"]+=$issue->getWeight();
}
}
foreach($tbstat as $k1=>$milestone) {
$tmp=$tbstat[$k1]["stat"];
ksort($tmp);
$tbstat[$k1]["stat"]=$tmp;
}
foreach($tbstat as $k1=>$milestone) {
foreach($tbstat[$k1]["stat"] as $k2=>$statut) {
$keysort = array_column($tbstat[$k1]["stat"][$k2]["labels"], 'label');
array_multisort($keysort, SORT_ASC, $tbstat[$k1]["stat"][$k2]["labels"]);
}
}
return $this->render($this->render.'stat.html.twig', [
'useheader' => true,
'usesidebar' => false,
'usetitle' => $data->getName(),
'giteaassignees' => $giteaassignees,
'giteacolumns' => $giteacolumns,
'giteamilestones' => $giteamilestones,
'giteateams' => $giteateams,
'giteaprioritys' => $giteaprioritys,
'giteatypes' => $giteatypes,
'gitealabels' => $gitealabels,
'filtermilestones' => $filtermilestones,
'filterteams' => $filterteams,
'showfilters' => $showfilters,
$this->data => $data,
'tbstat' => $tbstat,
]); ]);
} }
public function info($id,Request $request) public function info($id,Request $request)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$viewclosed = $request->getSession()->get("viewclosed"); $scrum=$em->getRepository($this->entity)->find($id);
if(!$scrum) return $this->redirectToRoute($this->route);
//$em->getRepository("App:Scrum")->getGitea($scrum,$giteaassignees,$giteacolumns,$giteamilestones,$giteateams,$giteaprioritys,$giteatypes,$gitealabels);
// Rechercher du scrum en cours $tbestim=$this->getEstim($scrum);
$scrum=$em->getRepository("App:Scrum")->find($id);
if(!$scrum) return new JsonResponse(['message' => 'No Issue'], 403);
$giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open"));
$weights=[];
foreach($giteaissues as $giteaissue) {
$scrumissue=$em->getRepository("App:Scrumissue")->findOneBy(["scrum"=>$scrum,"giteaid"=>$giteaissue->id]);
if($scrumissue) {
if($giteaissue->milestone) $milestoneid=$giteaissue->milestone->id;
else $milestoneid=-100;
if(!array_key_exists($milestoneid,$weights)) $weights[$milestoneid]=0;
$weights[$milestoneid]=$weights[$milestoneid]+$scrumissue->getWeight();
}
}
$output=[]; $output=[];
$output["weights"]=$weights; $output["tbestim"]=$tbestim;
return new JsonResponse($output); return new JsonResponse($output);
} }
private function getEstim($scrum) {
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$firstcolumn=$em->getRepository('App:Scrumcolumn')->findOneBy(["scrum"=>$scrum], ['rowid' => 'ASC']);
if(!$firstcolumn) return $this->redirectToRoute($this->route);
// Création du tableau des issues
$issues=$scrum->getScrumissues();
$tbissues=[];
$tbcols=[];
$tbjals=[];
$tbsprs=[];
$tbestim=[];
$viewclosed = $this->get('session')->get("viewclosed");
if($viewclosed=="false") {
$giteamilestones=$this->giteaservice->getMilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=open");
$giteamilestones=array_column($giteamilestones,"id");
}
foreach($issues as $issue) {
// bypass jalon closed
if($viewclosed=="false") {
/*
if(array_key_exists("milestone",$issue->getGiteajson()) && !empty($issue->getGiteajson()["milestone"]) && $issue->getGiteajson()["milestone"])
dump($issue->getGiteajson()["milestone"]["title"]);
*/
if(array_key_exists("milestone",$issue->getGiteajson()) && !empty($issue->getGiteajson()["milestone"]) && !in_array($issue->getGiteajson()["milestone"]["id"],$giteamilestones))
continue;
}
//if($viewclosed=="false"&&($issue->getGiteastate()=="closed"||($issue->getScrumsprint()&&$issue->getScrumsprint()->getClosed()))) continue;
// Ids
$idcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getId():$firstcolumn->getId());
$idjal=($issue->getGiteamilestone()?$issue->getGiteamilestone():-100);
$idspr=($issue->getScrumsprint()?$issue->getScrumsprint()->getId():-100);
// Roworders
$rowcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getRowid():$firstcolumn->getRowid());
$rowjal=($issue->getGiteaMilestonename()?$issue->getGiteaMilestonename():-100);
$rowspr=($issue->getScrumsprint()?$issue->getScrumsprint()->getRowid():-100);
// Names
$nmcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getName():$firstcolumn->getName());
$nmjal=($issue->getGiteaMilestonename()?$issue->getGiteaMilestonename():"Aucun");
$nmspr=($issue->getScrumsprint()?$issue->getScrumsprint()->getName():"Aucun");
// Idgiteas
$gicol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getGiteaid():$firstcolumn->getGiteaid());
$gijal=($issue->getGiteaMilestone()?$issue->getGiteaMilestone():-100);
if(!array_key_exists($idjal,$tbestim)) {
$tbestim[$idjal] = [
"rowjal" => $rowjal,
"idjal" => $idjal,
"gijal" => $gijal,
"nmjal" => $nmjal,
"nbjrs" => 0,
"nbiss" => 0,
"columns" => [],
"sprints" => [],
];
}
if(!array_key_exists($idspr,$tbestim[$idjal]["sprints"])) {
$tbestim[$idjal]["sprints"][$idspr] = [
"rowspr" => $rowspr,
"idspr" => $idspr,
"nmspr" => $nmspr,
"nbjrs" => 0,
"nbiss" => 0,
"columns" => [],
];
}
if(!array_key_exists("clos",$tbestim[$idjal]["columns"])) {
$tbestim[$idjal]["columns"]["clos"] = [
"rowcol" => 1000,
"idcol" => "clos",
"gicol" => "clos",
"nmcol" => "Clos",
"color" => "000000",
"nbjrs" => 0,
"nbiss" => 0,
];
}
if(!array_key_exists($idcol,$tbestim[$idjal]["columns"])) {
$tbestim[$idjal]["columns"][$idcol] = [
"rowcol" => $rowcol,
"idcol" => $idcol,
"gicol" => $gicol,
"nmcol" => $nmcol,
"color" => ($issue->getScrumcolumn()?$issue->getScrumcolumn()->getGiteajson()["color"]:$firstcolumn->getGiteajson()["color"]),
"nbjrs" => 0,
"nbiss" => 0,
];
}
if(!array_key_exists("clos",$tbestim[$idjal]["sprints"][$idspr]["columns"])) {
$tbestim[$idjal]["sprints"][$idspr]["columns"]["clos"] = [
"rowcol" => 10000,
"idcol" => "clos",
"gicol" => "clos",
"nmcol" => "Clos",
"color" => "000000",
"nbjrs" => 0,
"nbiss" => 0,
];
}
if(!array_key_exists($idcol,$tbestim[$idjal]["sprints"][$idspr]["columns"])) {
$tbestim[$idjal]["sprints"][$idspr]["columns"][$idcol] = [
"rowcol" => $rowcol,
"idcol" => $idcol,
"gicol" => $gicol,
"nmcol" => $nmcol,
"color" => ($issue->getScrumcolumn()?$issue->getScrumcolumn()->getGiteajson()["color"]:$firstcolumn->getGiteajson()["color"]),
"nbjrs" => 0,
"nbiss" => 0,
];
}
// On cumule les estimations
if($issue->getGiteastate()=="closed") $idcol="clos";
$tbestim[$idjal]["nbjrs"]+=$issue->getWeight();
$tbestim[$idjal]["columns"][$idcol]["nbjrs"]+=$issue->getWeight();
$tbestim[$idjal]["sprints"][$idspr]["nbjrs"]+=$issue->getWeight();
$tbestim[$idjal]["sprints"][$idspr]["columns"][$idcol]["nbjrs"]+=$issue->getWeight();
$tbestim[$idjal]["nbiss"]++;
$tbestim[$idjal]["columns"][$idcol]["nbiss"]++;
$tbestim[$idjal]["sprints"][$idspr]["nbiss"]++;
$tbestim[$idjal]["sprints"][$idspr]["columns"][$idcol]["nbiss"]++;
$this->formatDec($tbestim[$idjal]["nbjrs"]);
$this->formatDec($tbestim[$idjal]["columns"][$idcol]["nbjrs"]);
$this->formatDec($tbestim[$idjal]["sprints"][$idspr]["nbjrs"]);
$this->formatDec($tbestim[$idjal]["sprints"][$idspr]["columns"][$idcol]["nbjrs"]);
}
// dd("stop");
$keysort = array_column($tbestim, 'nmjal');
array_multisort($keysort, SORT_DESC, $tbestim);
foreach($tbestim as $keyj => $jalon) {
$keysort = array_column($jalon["sprints"],"nmspr");
array_multisort($keysort, SORT_DESC, $tbestim[$keyj]["sprints"]);
}
return $tbestim;
}
private function formatDec(&$number) {
if(strpos(strval($number), '.') !== false) $number=number_format($number,1);
else $number=intval($number);
}
protected function getErrorForm($id,$form,$request,$data,$mode) { protected function getErrorForm($id,$form,$request,$data,$mode) {
if ($form->get('submit')->isClicked()&&$mode=="delete") { if ($form->get('submit')->isClicked()&&$mode=="delete") {
} }

View File

@ -35,7 +35,7 @@ class ScrumcolumnController extends AbstractController
// Récupérer les repos de gitea // Récupérer les repos de gitea
$gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]); $gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>"); if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
// Création du formulaire // Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels)); $form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels));

View File

@ -12,6 +12,7 @@ use App\Entity\Scrumissue as Entity;
use App\Form\ScrumissueType as Form; use App\Form\ScrumissueType as Form;
use App\Service\giteaService; use App\Service\giteaService;
use stdClass;
class ScrumissueController extends AbstractController class ScrumissueController extends AbstractController
{ {
@ -31,8 +32,10 @@ class ScrumissueController extends AbstractController
$newcolumn=$request->get('newcolumn'); $newcolumn=$request->get('newcolumn');
$oldmilestone=$request->get('oldmilestone'); $oldmilestone=$request->get('oldmilestone');
$newmilestone=$request->get('newmilestone'); $newmilestone=$request->get('newmilestone');
$oldsprint=$request->get('oldsprint');
$newsprint=$request->get('newsprint');
if($oldcolumn!=$newcolumn||$oldmilestone!=$newmilestone) { if($oldcolumn!=$newcolumn||$oldmilestone!=$newmilestone||$oldsprint!=$newsprint) {
// Rechercher l'issue en cours // Rechercher l'issue en cours
$scrumissue=$em->getRepository("App:Scrumissue")->find($id); $scrumissue=$em->getRepository("App:Scrumissue")->find($id);
if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403); if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403);
@ -69,15 +72,31 @@ class ScrumissueController extends AbstractController
if(!$return) return new JsonResponse(['message' => 'No API patchIssue'], 403); if(!$return) return new JsonResponse(['message' => 'No API patchIssue'], 403);
} }
// Mettre à jour le sprint
if($oldsprint!=$newsprint) {
$scrumissue->setScrumsprint(null);
$sprint=$em->getRepository("App:Scrumsprint")->find($newsprint);
if($sprint) {
$scrumissue->setScrumsprint($sprint);
}
$em->flush();
}
// Récupérer l'issue modifiée pour mettre à jour la date de modification gitea // Récupérer l'issue modifiée pour mettre à jour la date de modification gitea
$giteaissue=$this->giteaservice->getIssue($scrumissue->getScrum()->getGiteajson()["owner"]["login"],$scrumissue->getScrum()->getGiteajson()["name"],$scrumissue->getGiteanumber()); $giteaissue=$this->giteaservice->getIssue($scrumissue->getScrum()->getGiteajson()["owner"]["login"],$scrumissue->getScrum()->getGiteajson()["name"],$scrumissue->getGiteanumber());
if(!$giteaissue) return new JsonResponse(['message' => 'No API getIssue'], 403); if(!$giteaissue) return new JsonResponse(['message' => 'No API getIssue'], 403);
$updatedate=new \DateTime($giteaissue->updated_at); $updateissue=new \DateTime(json_decode(json_encode($giteaissue), true)["updated_at"]);
if($updatedate > $scrumissue->getScrum()->getUpdatedate()) { $updateissue->setTimezone(new \DateTimeZone("UTC"));
$scrumissue->getScrum()->setUpdatedate(new \DateTime($giteaissue->updated_at)); $em->getRepository("App:Scrum")->majissue($scrumissue,$giteaissue,false);
$lastupdate=$scrumissue->getScrum()->getUpdatedate();
if($updateissue > $lastupdate) {
$scrumissue->getScrum()->setUpdatedate($updateissue);
$em->persist($scrumissue->getScrum()); $em->persist($scrumissue->getScrum());
$em->flush(); $em->flush();
return new JsonResponse($updatedate->format("Ymd H:i:s")); return new JsonResponse($updateissue->format("Ymd H:i:s"));
} }
} }
return new JsonResponse(false); return new JsonResponse(false);
@ -120,10 +139,77 @@ class ScrumissueController extends AbstractController
$output=[]; $output=[];
$output["weight"]=$scrumissue->getWeight(); $output["weight"]=$scrumissue->getWeight();
$output["giteajson"]=$scrumissue->getGiteajson();
$output["notes"]=$scrumissue->getNotes();
return new JsonResponse($output); return new JsonResponse($output);
} }
public function view($id, Request $request) {
$em = $this->getDoctrine()->getManager();
$issue=$em->getRepository("App:Scrumissue")->find($id);
$scrum = $issue->getScrum();
$repoid = $scrum->getGiteaid();
$repoowner = $scrum->getGiteajson()["owner"]["login"];
$reponame = $scrum->getGiteajson()["name"];
$repo=$this->giteaservice->getRepo($repoid);
$giteaissue=$this->giteaservice->getIssue($repoowner,$reponame,$issue->getGiteanumber());
$giteaissue->body = $this->giteaservice->markdown("/".$scrum->getGiteajson()["full_name"],"comment",$giteaissue->body);
$giteaissue->comments=$this->giteaservice->getIssueComments($repoowner,$reponame,$giteaissue->number);
$giteaissue->statuslife=$issue->getScrumcolumn()->getName();
$giteaissue->weight=$issue->getWeight();
$giteaissue->nineid=$issue->getId();
$giteaissue->sprint=($issue->getScrumsprint()?$issue->getScrumsprint()->getName():null);
foreach($giteaissue->comments as $keycomment => $comment) {
$giteaissue->comments[$keycomment]->body=$this->giteaservice->markdown("/".$scrum->getGiteajson()["full_name"],"comment",$giteaissue->comments[$keycomment]->body);
}
$giteaissue->timelines=$this->giteaservice->getIssueTimelines($repoowner,$reponame,$giteaissue->number);
$giteaissue->labelhistos=[];
$giteaissue->refs=[];
foreach($giteaissue->timelines as $key => $timeline) {
if($timeline->type == "label"){
$tmp=new stdClass();
$tmp->label=($timeline->body==1?"Ajout Label":"Suppression Label")." <i>".$timeline->label->name."<i>";
$tmp->user=$timeline->user;
$tmp->created_at=$timeline->created_at;
array_push($giteaissue->labelhistos,$tmp);
unset($giteaissue->timelines[$key]);
}
elseif($timeline->type == "comment_ref" || $timeline->type == "pull_ref" || $timeline->type == "issue_ref"){
$tmp=new stdClass();
$tmp->label ="<a href='".$timeline->ref_issue->html_url."' target='_blank'>";
$tmp->label.=($timeline->type=="pull_ref"?"Request":"Issue")." = ";
$tmp->label.="#".$timeline->ref_issue->number." = ".$timeline->ref_issue->title."</a></i>";
$tmp->user=$timeline->user;
$tmp->created_at=$timeline->created_at;
array_push($giteaissue->refs,$tmp);
unset($giteaissue->timelines[$key]);
}
}
// Affichage du formulaire
return $this->render('Scrum/issue.html.twig', [
'useheader' => false,
'usesidebar' => false,
'maxwidth' => true,
'repo' => $repo,
'issue' => $giteaissue,
]);
}
public function update(Request $request) public function update(Request $request)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@ -154,39 +240,123 @@ class ScrumissueController extends AbstractController
return new JsonResponse($weights); return new JsonResponse($weights);
} }
public function block(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id=$request->get('id');
$issueblocked=$request->get('issueblocked');
// Rechercher l'issue en cours
$scrumissue=$em->getRepository("App:Scrumissue")->find($id);
if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403);
// Bloquer l'issue
$response=$this->giteaservice->postIssueblocks($scrumissue->getScrum()->getGiteajson()["owner"]["login"],$scrumissue->getScrum()->getGiteajson()["name"],$scrumissue->getGiteanumber(),$issueblocked);
if(!$response) return new JsonResponse(['message' => 'Error api'], 403);
return new JsonResponse([]);
}
public function unblock(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id=$request->get('id');
// Rechercher l'issue en cours
$scrumissue=$em->getRepository("App:Scrumissue")->find($id);
if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403);
if(!$scrumissue->getScrumissueblock()) return new JsonResponse(['message' => 'No Issue'], 403);
$issueblocked=$scrumissue->getScrumissueblock()->getGiteanumber();
// Débloquer l'issue
$response=$this->giteaservice->deleteIssueblocks($scrumissue->getScrum()->getGiteajson()["owner"]["login"],$scrumissue->getScrum()->getGiteajson()["name"],$scrumissue->getGiteanumber(),$issueblocked);
if(!$response) return new JsonResponse(['message' => 'Error api'], 403);
return new JsonResponse([]);
}
public function assigne(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id=$request->get('id');
$assignees=$request->get('assignees');
// Rechercher l'issue en cours
$scrumissue=$em->getRepository("App:Scrumissue")->find($id);
if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403);
// Affecter l'issue
$response=$this->giteaservice->patchissue($scrumissue->getScrum()->getGiteajson()["owner"]["login"],$scrumissue->getScrum()->getGiteajson()["name"],$scrumissue->getGiteanumber(),["assignees"=>($assignees?$assignees:[])]);
if(!$response) return new JsonResponse(['message' => 'Error api'], 403);
return new JsonResponse([]);
}
public function notes(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id=$request->get('id');
$notes=$request->get('notes');
// Rechercher l'issue en cours
$scrumissue=$em->getRepository("App:Scrumissue")->find($id);
if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403);
$scrumissue->setNotes($notes);
$em->flush();
return new JsonResponse([]);
}
public function color(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id=$request->get('id');
$color=$request->get('color');
// Rechercher l'issue en cours
$scrumissue=$em->getRepository("App:Scrumissue")->find($id);
if(!$scrumissue) return new JsonResponse(['message' => 'No Issue'], 403);
$scrumissue->setColor($color);
$em->flush();
return new JsonResponse([]);
}
public function ctrlchange(Request $request) public function ctrlchange(Request $request)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$id=$request->get('id'); $id=$request->get('id');
$lastupdate=new \DateTime($request->get('lastupdate')); $lastupdate=new \DateTime($request->get('lastupdate'));
$lastupdate->setTimezone(new \DateTimeZone("UTC"));
$lastupdate->add(new \DateInterval("PT1M"));
//var_dump($lastupdate->format(\DateTime::RFC3339_EXTENDED));
$scrum=$em->getRepository("App:Scrum")->find($id); $scrum=$em->getRepository("App:Scrum")->find($id);
if(!$scrum) return new JsonResponse(['message' => 'No Scrum'], 403); if(!$scrum) return new JsonResponse(['message' => 'No Scrum'], 403);
$giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]); $giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?since=".urlencode($lastupdate->format(\DateTime::RFC3339_EXTENDED)));
if(!is_array($giteaissues)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>");
foreach($giteaissues as $giteaissue) { return new Response((!empty($giteaissues)?"1":"0"));
// On ne prend pas les pull request
if(!is_null($giteaissue->pull_request))
continue;
$scrumissue=$em->getRepository("App:Scrumissue")->findOneBy(["scrum"=>$scrum,"giteaid"=>$giteaissue->id]);
if(!$scrumissue)
return new JsonResponse(true);
$fgissueupdated=false;
$updatedate=new \DateTime(json_decode(json_encode($giteaissue), true)["updated_at"]);
if($updatedate>$lastupdate) {
$fgissueupdated=true;
} }
if($fgissueupdated) public function getpoker($userid,$issueid,Request $request) {
return new JsonResponse(true); return new Response(0);
} }
return new JsonResponse(false); public function setpoker($userid,$issueid,Request $request) {
} }
} }

View File

@ -35,7 +35,7 @@ class ScrumpriorityController extends AbstractController
// Récupérer les repos de gitea // Récupérer les repos de gitea
$gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]); $gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>"); if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
// Création du formulaire // Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels)); $form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels));

View File

@ -0,0 +1,196 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Form\FormError;
use App\Entity\Scrumsprint as Entity;
use App\Form\ScrumsprintType as Form;
use App\Service\giteaService;
class ScrumsprintController extends AbstractController
{
private $data = "scrumsprint";
private $route = "app_scrumsprint";
private $render = "Scrumsprint/";
private $entity = "App:Scrumsprint";
public function __construct(giteaService $giteaservice) { $this->giteaservice = $giteaservice; }
public function submit($scrumid, Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$scrum=$em->getRepository("App:Scrum")->find($scrumid);
$data = new Entity();
$data->setScrum($scrum);
$last = $em->getRepository('App:Scrumsprint')->findOneBy(["scrum"=>$scrum], ['rowid' => 'DESC']);
if(!$last) $data->setRowid(0);
else $data->setRowid($last->getRowid()+1);
// Récupérer les repos de gitea
$giteamilestones=$this->giteaservice->getmilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($giteamilestones)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
// Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"submit","giteamilestones"=>$giteamilestones));
// Récupération des data du formulaire
$form->handleRequest($request);
// Sur erreur
$this->getErrorForm(null,$form,$request,$data,"submit");
// Sur validation
if ($form->get('submit')->isClicked() && $form->isValid()) {
$data = $form->getData();
$getmilestone=$this->giteaservice->getmilestone($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],$data->getGiteamilestone());
$data->setGiteamilestonename($getmilestone->title);
$em->persist($data);
$em->flush();
// Retour à la liste
return $this->render($this->render.'close.html.twig');
}
// Affichage du formulaire
return $this->render($this->render.'edit.html.twig', [
'useheader' => false,
'usesidebar' => false,
$this->data => $data,
'mode' => 'submit',
'form' => $form->createView()
]);
}
public function update($id,Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$data=$em->getRepository($this->entity)->find($id);
$scrum=$data->getScrum();
// Récupérer les repos de gitea
$giteamilestones=$this->giteaservice->getmilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
// Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"update","giteamilestones"=>$giteamilestones));
// Récupération des data du formulaire
$form->handleRequest($request);
// Sur erreur
$this->getErrorForm(null,$form,$request,$data,"update");
// Sur validation
if ($form->get('submit')->isClicked() && $form->isValid()) {
$data = $form->getData();
$getmilestone=$this->giteaservice->getmilestone($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],$data->getGiteamilestone());
$data->setGiteamilestonename($getmilestone->title);
$em->persist($data);
$em->flush();
// Retour à la liste
return $this->render($this->render.'close.html.twig');
}
// Affichage du formulaire
return $this->render($this->render.'edit.html.twig', [
'useheader' => false,
'usesidebar' => false,
$this->data => $data,
'mode' => 'update',
'form' => $form->createView()
]);
}
public function delete($id,Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$data=$em->getRepository($this->entity)->find($id);
// Controle avant suppression
$error=false;
if($id<0) $error=true;
if($error)
return $this->redirectToRoute($this->route."_update",["id"=>$id]);
else {
$em->remove($data);
$em->flush();
// Retour à la liste
return $this->render($this->render.'close.html.twig');
}
}
public function select($scrumid, Request $request)
{
// S'assurer que c'est un appel ajax
if (!$request->isXmlHttpRequest()) {
return new JsonResponse(array('message' => 'Interdit'), 400);
}
$em = $this->getDoctrine()->getManager();
$scrum=$em->getRepository("App:Scrum")->find($scrumid);
$scrumsprints = $scrum->getScrumsprints();
$output=array();
foreach($scrumsprints as $scrumsprint) {
$title = "Jalon = ".$scrumsprint->getGiteamilestonename()."<br>";
$title.= "Sprint = ".$scrumsprint->getName();
array_push($output,array("id"=>$scrumsprint->getId(),"name"=>$title,"closed"=>$scrumsprint->getClosed()));
}
$response = new Response(json_encode($output));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function order($scrumid, Request $request)
{
$em = $this->getDoctrine()->getManager();
$scrumsprintids=explode(",",$request->get('lstordered'));
$i=1;
foreach($scrumsprintids as $id) {
$scrumsprint=$em->getRepository($this->entity)->find($id);
if($scrumsprint) {
$scrumsprint->setRowid($i);
$em->persist($scrumsprint);
$em->flush();
}
$i++;
}
$response = new Response();
$response->headers->set('Content-Type', 'application/json');
return $response;
}
protected function getErrorForm($id,$form,$request,$data,$mode) {
if ($form->get('submit')->isClicked()&&$mode=="delete") {
}
if ($form->get('submit')->isClicked() && $mode=="submit") {
}
if ($form->get('submit')->isClicked() && ($mode=="submit" || $mode=="update")) {
}
if ($form->get('submit')->isClicked() && !$form->isValid()) {
$this->get('session')->getFlashBag()->clear();
$errors = $form->getErrors();
foreach( $errors as $error ) {
$request->getSession()->getFlashBag()->add("error", $error->getMessage());
}
}
}
}

View File

@ -35,7 +35,7 @@ class ScrumteamController extends AbstractController
// Récupérer les repos de gitea // Récupérer les repos de gitea
$gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]); $gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>"); if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
// Création du formulaire // Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels)); $form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels));

View File

@ -35,7 +35,7 @@ class ScrumtypeController extends AbstractController
// Récupérer les repos de gitea // Récupérer les repos de gitea
$gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]); $gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>"); if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
// Création du formulaire // Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels)); $form = $this->createForm(Form::class,$data,array("mode"=>"submit","gitealabels"=>$gitealabels));

View File

@ -135,16 +135,8 @@ class SecurityController extends AbstractController
} }
public function loginOAUTH() { public function loginOAUTH() {
/*
OAUTH_CLIENTID=
OAUTH_CLIENTSECRET=
OAUTH_LOGINURL=https://forge.cadoles.com/login/oauth/authorize
OAUTH_LOGOUTURL=https://forge.cadoles.com/user/logout
OAUTH_TOKENURL=https://forge.cadoles.com/login/oauth/access_token
*/
// https://[YOUR-GITEA-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI& response_type=code&state=STATE
$callback=$this->generateUrl('app_login_callback', array(), UrlGeneratorInterface::ABSOLUTE_URL); $callback=$this->generateUrl('app_login_callback', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$callback=str_replace("http://",$this->getParameter("appProtocol")."://",$callback);
$this->get('session')->set('giteacallback', $callback); $this->get('session')->set('giteacallback', $callback);
$url=$this->getParameter("oauthLoginurl")."?client_id=".$this->getParameter("oauthClientid")."&redirect_uri=".$callback."&response_type=code&state=STATE"; $url=$this->getParameter("oauthLoginurl")."?client_id=".$this->getParameter("oauthClientid")."&redirect_uri=".$callback."&response_type=code&state=STATE";
return $this->redirect($url); return $this->redirect($url);
@ -208,10 +200,15 @@ class SecurityController extends AbstractController
// Redirection // Redirection
$redirect = $this->get('session')->get("_security.main.target_path"); $redirect = $this->get('session')->get("_security.main.target_path");
if($redirect) if($redirect) {
$redirect=str_replace("http://",$this->getParameter("appProtocol")."://",$redirect);
}
else {
$redirect=$this->generateUrl('app_home');
$redirect=str_replace("http://",$this->getParameter("appProtocol")."://",$redirect);
}
return $this->redirect($redirect); return $this->redirect($redirect);
else
return $this->redirect($this->generateUrl('app_home'));
} }

View File

@ -67,6 +67,12 @@ class Scrum
*/ */
private $scrumcolumns; private $scrumcolumns;
/**
* @ORM\OneToMany(targetEntity="Scrumsprint", mappedBy="scrum", cascade={"persist"}, orphanRemoval=true)
* @ORM\OrderBy({"rowid" = "ASC"})
*/
private $scrumsprints;
/** /**
* @ORM\OneToMany(targetEntity="Scrumissue", mappedBy="scrum", cascade={"persist"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="Scrumissue", mappedBy="scrum", cascade={"persist"}, orphanRemoval=true)
* @ORM\OrderBy({"giteamilestonename" = "DESC", "rowid" = "ASC"}) * @ORM\OrderBy({"giteamilestonename" = "DESC", "rowid" = "ASC"})
@ -108,6 +114,18 @@ class Scrum
return $tab; return $tab;
} }
public function getScrumsprintstosee($viewclosed) {
$sprints=$this->getScrumsprints();
if($viewclosed=="false") {
foreach($sprints as $sprint) {
if($sprint->getClosed()) {
$sprints->removeElement($sprint);
}
}
}
return $sprints;
}
public function __construct() public function __construct()
{ {
$this->users = new ArrayCollection(); $this->users = new ArrayCollection();
@ -117,6 +135,7 @@ class Scrum
$this->scrumteams = new ArrayCollection(); $this->scrumteams = new ArrayCollection();
$this->scrumprioritys = new ArrayCollection(); $this->scrumprioritys = new ArrayCollection();
$this->scrumtypes = new ArrayCollection(); $this->scrumtypes = new ArrayCollection();
$this->scrumsprints = new ArrayCollection();
} }
public function getId(): ?int public function getId(): ?int
@ -378,4 +397,35 @@ class Scrum
return $this; return $this;
} }
/**
* @return Collection|Scrumsprint[]
*/
public function getScrumsprints(): Collection
{
return $this->scrumsprints;
}
public function addScrumsprint(Scrumsprint $scrumsprint): self
{
if (!$this->scrumsprints->contains($scrumsprint)) {
$this->scrumsprints[] = $scrumsprint;
$scrumsprint->setScrum($this);
}
return $this;
}
public function removeScrumsprint(Scrumsprint $scrumsprint): self
{
if ($this->scrumsprints->contains($scrumsprint)) {
$this->scrumsprints->removeElement($scrumsprint);
// set the owning side to null (unless already changed)
if ($scrumsprint->getScrum() === $this) {
$scrumsprint->setScrum(null);
}
}
return $this;
}
} }

View File

@ -49,8 +49,8 @@ class Scrumcolumn
private $scrum; private $scrum;
/** /**
* @ORM\OneToMany(targetEntity="Scrumissue", mappedBy="scrumcolumn", cascade={"persist"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="Scrumissue", mappedBy="scrumcolumn", cascade={"persist"}, orphanRemoval=false)
* @ORM\OrderBy({"giteamilestonename" = "DESC", "rowid" = "ASC"}) * @ORM\OrderBy({"giteamilestonename" = "DESC", "scrumsprint" = "DESC", "rowid" = "ASC"})
*/ */
private $scrumissues; private $scrumissues;

View File

@ -2,6 +2,8 @@
namespace App\Entity; namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -31,6 +33,16 @@ class Scrumissue
*/ */
private $weight=0; private $weight=0;
/**
* @ORM\Column(type="string", nullable=true)
*/
private $color;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $notes;
/** /**
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
*/ */
@ -69,15 +81,46 @@ class Scrumissue
private $giteajson; private $giteajson;
/** /**
* @ORM\ManyToOne(targetEntity="Scrum", inversedBy="Scrumissues") * @ORM\ManyToOne(targetEntity="Scrum", inversedBy="scrumissues")
*/ */
private $scrum; private $scrum;
/** /**
* @ORM\ManyToOne(targetEntity="Scrumcolumn", inversedBy="Scrumissues") * @ORM\ManyToOne(targetEntity="Scrumcolumn", inversedBy="scrumissues")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/ */
private $scrumcolumn; private $scrumcolumn;
/**
* @ORM\ManyToOne(targetEntity="Scrumsprint", inversedBy="scrumissues")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $scrumsprint;
/**
* @ORM\ManyToOne(targetEntity="Scrumissue", inversedBy="scrumissuedependencies")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $scrumissueblock;
/**
* @ORM\OneToMany(targetEntity="Scrumissue", mappedBy="scrumissueblock", cascade={"persist"}, orphanRemoval=false)
* @ORM\OrderBy({"giteanumber" = "ASC"})
*/
private $scrumissuedependencies;
/**
* @ORM\OneToMany(targetEntity="Userpoker", mappedBy="scrumissue", cascade={"persist"}, orphanRemoval=true)
*/
private $userpokers;
public function __construct()
{
$this->userpokers = new ArrayCollection();
$this->scrumissuedependcies = new ArrayCollection();
$this->scrumissuedependencies = new ArrayCollection();
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -215,6 +258,113 @@ class Scrumissue
return $this; return $this;
} }
public function getScrumsprint(): ?Scrumsprint
{
return $this->scrumsprint;
}
public function setScrumsprint(?Scrumsprint $scrumsprint): self
{
$this->scrumsprint = $scrumsprint;
return $this;
}
/**
* @return Collection|Userpoker[]
*/
public function getUserpokers(): Collection
{
return $this->userpokers;
}
public function addUserpoker(Userpoker $userpoker): self
{
if (!$this->userpokers->contains($userpoker)) {
$this->userpokers[] = $userpoker;
$userpoker->setScrumissue($this);
}
return $this;
}
public function removeUserpoker(Userpoker $userpoker): self
{
if ($this->userpokers->contains($userpoker)) {
$this->userpokers->removeElement($userpoker);
// set the owning side to null (unless already changed)
if ($userpoker->getScrumissue() === $this) {
$userpoker->setScrumissue(null);
}
}
return $this;
}
public function getScrumissueblock(): ?self
{
return $this->scrumissueblock;
}
public function setScrumissueblock(?self $scrumissueblock): self
{
$this->scrumissueblock = $scrumissueblock;
return $this;
}
/**
* @return Collection|Scrumissue[]
*/
public function getScrumissuedependencies(): Collection
{
return $this->scrumissuedependencies;
}
public function addScrumissuedependency(Scrumissue $scrumissuedependency): self
{
if (!$this->scrumissuedependencies->contains($scrumissuedependency)) {
$this->scrumissuedependencies[] = $scrumissuedependency;
$scrumissuedependency->setScrumissue($this);
}
return $this;
}
public function removeScrumissuedependency(Scrumissue $scrumissuedependency): self
{
if ($this->scrumissuedependencies->contains($scrumissuedependency)) {
$this->scrumissuedependencies->removeElement($scrumissuedependency);
// set the owning side to null (unless already changed)
if ($scrumissuedependency->getScrumissue() === $this) {
$scrumissuedependency->setScrumissue(null);
}
}
return $this;
}
public function getColor(): ?string
{
return $this->color;
}
public function setColor(?string $color): self
{
$this->color = $color;
return $this;
}
public function getNotes(): ?string
{
return $this->notes;
}
public function setNotes(?string $notes): self
{
$this->notes = $notes;
return $this;
}
} }

176
src/Entity/Scrumsprint.php Normal file
View File

@ -0,0 +1,176 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Scrumcolumn
*
* @ORM\Entity()
* @ORM\Table(name="scrumsprint")
*/
class Scrumsprint
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="name", type="string")
*
*/
private $name;
/**
* @ORM\Column(type="integer")
*/
private $rowid;
/**
* @ORM\Column(type="boolean")
*/
private $closed;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $giteamilestone;
/**
* @ORM\Column(type="string", nullable=true)
*
*/
private $giteamilestonename;
/**
* @ORM\ManyToOne(targetEntity="Scrum", inversedBy="scrumsprints")
*/
private $scrum;
/**
* @ORM\OneToMany(targetEntity="Scrumissue", mappedBy="scrumsprint", cascade={"persist"}, orphanRemoval=false)
*/
private $scrumissues;
public function __construct()
{
$this->scrumissues = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getRowid(): ?int
{
return $this->rowid;
}
public function setRowid(int $rowid): self
{
$this->rowid = $rowid;
return $this;
}
public function getGiteamilestone(): ?int
{
return $this->giteamilestone;
}
public function setGiteamilestone(?int $giteamilestone): self
{
$this->giteamilestone = $giteamilestone;
return $this;
}
public function getGiteamilestonename(): ?string
{
return $this->giteamilestonename;
}
public function setGiteamilestonename(?string $giteamilestonename): self
{
$this->giteamilestonename = $giteamilestonename;
return $this;
}
public function getScrum(): ?Scrum
{
return $this->scrum;
}
public function setScrum(?Scrum $scrum): self
{
$this->scrum = $scrum;
return $this;
}
/**
* @return Collection|Scrumissue[]
*/
public function getScrumissues(): Collection
{
return $this->scrumissues;
}
public function addScrumissue(Scrumissue $scrumissue): self
{
if (!$this->scrumissues->contains($scrumissue)) {
$this->scrumissues[] = $scrumissue;
$scrumissue->setScrumsprint($this);
}
return $this;
}
public function removeScrumissue(Scrumissue $scrumissue): self
{
if ($this->scrumissues->contains($scrumissue)) {
$this->scrumissues->removeElement($scrumissue);
// set the owning side to null (unless already changed)
if ($scrumissue->getScrumsprint() === $this) {
$scrumissue->setScrumsprint(null);
}
}
return $this;
}
public function getClosed(): ?bool
{
return $this->closed;
}
public function setClosed(bool $closed): self
{
$this->closed = $closed;
return $this;
}
}

View File

@ -85,6 +85,11 @@ class User implements UserInterface, \Serializable
*/ */
private $preference; private $preference;
/**
* @ORM\OneToMany(targetEntity="Userpoker", mappedBy="user", cascade={"persist"}, orphanRemoval=true)
*/
private $userpokers;
/** /**
* @ORM\ManyToMany(targetEntity="Group", inversedBy="users", cascade={"persist"}) * @ORM\ManyToMany(targetEntity="Group", inversedBy="users", cascade={"persist"})
* @ORM\JoinTable(name="usergroupe", * @ORM\JoinTable(name="usergroupe",
@ -107,6 +112,7 @@ class User implements UserInterface, \Serializable
{ {
$this->groups = new ArrayCollection(); $this->groups = new ArrayCollection();
$this->scrums = new ArrayCollection(); $this->scrums = new ArrayCollection();
$this->userpokers = new ArrayCollection();
} }
public function getUsername(): ?string public function getUsername(): ?string
@ -172,7 +178,7 @@ class User implements UserInterface, \Serializable
public function getDisplayname() public function getDisplayname()
{ {
return $this->firstname." ".$this->lastname; return $this->username;
} }
public function setId(int $id): self public function setId(int $id): self
@ -336,4 +342,35 @@ class User implements UserInterface, \Serializable
return $this; return $this;
} }
/**
* @return Collection|Userpoker[]
*/
public function getUserpokers(): Collection
{
return $this->userpokers;
}
public function addUserpoker(Userpoker $userpoker): self
{
if (!$this->userpokers->contains($userpoker)) {
$this->userpokers[] = $userpoker;
$userpoker->setUser($this);
}
return $this;
}
public function removeUserpoker(Userpoker $userpoker): self
{
if ($this->userpokers->contains($userpoker)) {
$this->userpokers->removeElement($userpoker);
// set the owning side to null (unless already changed)
if ($userpoker->getUser() === $this) {
$userpoker->setUser(null);
}
}
return $this;
}
} }

82
src/Entity/Userpoker.php Normal file
View File

@ -0,0 +1,82 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Userpoker
*
* @ORM\Entity()
* @ORM\Table(name="userpoker")
*/
class Userpoker
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="name", type="integer")
*
*/
private $nb;
/**
* @ORM\ManyToOne(targetEntity="Scrumissue", inversedBy="userpokers")
*/
private $scrumissue;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="userpokers")
*/
private $user;
public function getId(): ?int
{
return $this->id;
}
public function getNb(): ?int
{
return $this->nb;
}
public function setNb(int $nb): self
{
$this->nb = $nb;
return $this;
}
public function getScrumissue(): ?Scrumissue
{
return $this->scrumissue;
}
public function setScrumissue(?Scrumissue $scrumissue): self
{
$this->scrumissue = $scrumissue;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Tetranz\Select2EntityBundle\Form\Type\Select2EntityType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManager;
class ScrumsprintType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('submit',
SubmitType::class, [
"label" => "Valider",
"attr" => ["class" => "btn btn-success no-print"],
]
);
$builder->add('name',
TextType::class, [
"label" =>"Nom",
]
);
$choices=["non"=>0,"oui"=>1];
$builder->add('closed',
ChoiceType::class, ['choices' => $choices]
);
$choices=[];
foreach($options["giteamilestones"] as $milestone) {
$choices[$milestone->title]=$milestone->id;
}
$builder->add('giteamilestone',
ChoiceType::class, [
"label" => "Jalon Gitea",
"choices" => $choices,
"disabled" => ($options["mode"]=="submit"?false:true),
"placeholder" => "Selectionnez un label gitea",
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\Entity\Scrumsprint',
'mode' => 'string',
'giteamilestones' => 'string',
));
}
}

View File

@ -13,13 +13,32 @@ class ScrumRepository extends ServiceEntityRepository
{ {
private $session; private $session;
private $firstcolumn; private $firstcolumn;
private $datescrumupdate; private $totalTime;
private $startTime;
private $showTime;
public function __construct(ManagerRegistry $registry,giteaService $giteaservice,SessionInterface $session) public function __construct(ManagerRegistry $registry,giteaService $giteaservice,SessionInterface $session)
{ {
parent::__construct($registry, Scrum::class); parent::__construct($registry, Scrum::class);
$this->giteaservice = $giteaservice; $this->giteaservice = $giteaservice;
$this->session = $session; $this->session = $session;
$this->showTime = false;
}
private function showtime($istart,$title="") {
if($this->showTime) {
if($istart) {
$this->totalTime = microtime(true);
$this->startTime = microtime(true);
}
else {
$endTime = microtime(true);
$executionTime = $endTime - $this->startTime;
$totalTime = $endTime - $this->totalTime;
dump($executionTime." / ".$totalTime." = ".$title);
$this->startTime = microtime(true);
}
}
} }
public function findByUser($user) { public function findByUser($user) {
@ -36,9 +55,81 @@ class ScrumRepository extends ServiceEntityRepository
return $scrums; return $scrums;
} }
} }
public function getGitea($scrum,&$giteaassignees,&$giteacolumns,&$giteamilestones,&$giteateams,&$giteaprioritys,&$giteatypes,&$gitealabels, $forcereload=false) { public function getGitea($scrum,&$giteaassignees,&$giteacolumns,&$giteamilestones,&$giteateams,&$giteaprioritys,&$giteatypes,&$gitealabels, $forcereload=false) {
$this->showtime(true);
$lastupdate = $scrum->getUpdatedate();
if(!$lastupdate) $lastupdate = new \DateTime(("19000101"));
$lastupdate->sub(new \DateInterval('PT30M'));
$viewclosed = $this->session->get("viewclosed"); $viewclosed = $this->session->get("viewclosed");
// Récupérer les labels de gitea
$gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
$giteaidlabels=array_column($gitealabels, 'id');
// Récupérer les jalons de gitea
$giteamilestones=$this->giteaservice->getMilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($giteamilestones)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
$giteaidmilestones=array_column($giteamilestones, 'id');
// Temps d'execution
$this->showtime(false,"Récupération gitealabels & giteamilestones");
// S'assurer que nos colonnes existes toujours
$scrumcolumns=$scrum->getScrumcolumns();
foreach($scrumcolumns as $scrumcolumn) {
if(!in_array($scrumcolumn->getGiteaid(),$giteaidlabels)) {
$this->_em->remove($scrumcolumn);
$this->_em->flush();
}
}
// S'assurer que nos teams existes toujours
$scrumteams=$scrum->getScrumteams();
foreach($scrumteams as $scrumteam) {
if(!in_array($scrumteam->getGiteaid(),$giteaidlabels)) {
$this->_em->remove($scrumteam);
$this->_em->flush();
}
}
// S'assurer que nos prioritys existes toujours
$scrumprioritys=$scrum->getScrumprioritys();
foreach($scrumprioritys as $scrumpriority) {
if(!in_array($scrumpriority->getGiteaid(),$giteaidlabels)) {
$this->_em->remove($scrumpriority);
$this->_em->flush();
}
}
// S'assurer que nos types existes toujours
$scrumtypes=$scrum->getScrumtypes();
foreach($scrumtypes as $scrumtype) {
if(!in_array($scrumtype->getGiteaid(),$giteaidlabels)) {
$this->_em->remove($scrumtype);
$this->_em->flush();
}
}
// Temps d'execution
$this->showtime(false,"S'assurer que nos labels colonnes teams prioritys types existes toujours");
// S'assurer que nos sprint sont toujours lié à un jalon existant
$scrumsprints=$scrum->getScrumsprints();
foreach($scrumsprints as $scrumsprint) {
if(!in_array($scrumsprint->getGiteamilestone(),$giteaidmilestones)) {
$this->_em->remove($scrumsprint);
$this->_em->flush();
}
}
// Temps d'execution
$this->showtime(false,"S'assurer que nos sprint sont toujours lié à un jalon existant");
// Récupérer le dernier order // Récupérer le dernier order
$last = $this->_em->getRepository('App:Scrumissue')->findOneBy(["scrum"=>$scrum], ['rowid' => 'DESC']); $last = $this->_em->getRepository('App:Scrumissue')->findOneBy(["scrum"=>$scrum], ['rowid' => 'DESC']);
if(!$last) $lastrowid=-1; if(!$last) $lastrowid=-1;
@ -82,54 +173,42 @@ class ScrumRepository extends ServiceEntityRepository
else else
$giteaassignees=[]; $giteaassignees=[];
$giteacollaborators=$this->giteaservice->getOrgateams($scrum->getGiteajson()["owner"]["login"]); // Temps d'execution
if($giteacollaborators&&is_array($giteacollaborators)) { $this->showtime(false,"Récupérer info ninegitea");
foreach($giteacollaborators as $team) {
$giteamembers=$this->giteaservice->getTeammembers($team->id); // Récupérer les intervenants
if($giteamembers&&is_array($giteamembers)) { $giteacollaborators=$this->giteaservice->getAssignees($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
foreach($giteamembers as $giteamember) { if(!is_array($giteacollaborators)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
if(!in_array($giteamember,$giteaassignees))
array_push($giteaassignees,$giteamember);
}
}
}
}
$giteacollaborators=$this->giteaservice->getCollaborators($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]);
if(!is_array($giteacollaborators)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>");
foreach($giteacollaborators as $giteacollaborator) { foreach($giteacollaborators as $giteacollaborator) {
if(!in_array($giteacollaborator,$giteaassignees)) if(!in_array($giteacollaborator,$giteaassignees))
array_push($giteaassignees,$giteacollaborator); array_push($giteaassignees,$giteacollaborator);
} }
// Temps d'execution
$this->showtime(false,"Récupérer les intervenants");
// Récupérer les milestones de gitea // Récupérer les milestones de gitea
$giteamilestones=$this->giteaservice->getMilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open")); $giteamilestones=$this->giteaservice->getMilestones($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open"));
if(!is_array($giteamilestones)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>"); if(!is_array($giteamilestones)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
$keysort = array_column($giteamilestones, 'title'); $keysort = array_column($giteamilestones, 'title');
array_multisort($keysort, SORT_DESC, $giteamilestones); array_multisort($keysort, SORT_DESC, $giteamilestones);
// Récupérer les labels de gitea // Temps d'execution
$gitealabels=$this->giteaservice->getLabels($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"]); $this->showtime(false,"Récupérer les milestones");
if(!is_array($gitealabels)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>");
foreach($gitealabels as $key => $gitealabel) {
if(in_array($gitealabel->id,$giteacolumns)||in_array($gitealabel->id,$giteateams)||in_array($gitealabel->id,$giteaprioritys)||in_array($gitealabel->id,$giteatypes))
unset($gitealabels[$key]);
}
// Récupérer les issues de gitea // Récupérer les issues de gitea
$giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=".($viewclosed=="true"?"all":"open")); $giteaissues=$this->giteaservice->getIssues($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],"?state=all".(!$forcereload?"&since=".urlencode($lastupdate->format(\DateTime::RFC3339_EXTENDED)):""));
if(!is_array($giteaissues)) die("Probleme de connexion avec gitea veuillez vous <a href='/ninegitea/logout'>reconnecter</a>"); if(!is_array($giteaissues)) die("Probleme de connexion avec gitea veuillez vous <a href='/logout'>reconnecter</a>");
// Temps d'execution
$this->showtime(false,"Récupérer les issues");
// Génération des issues // Génération des issues
$tbgiteaissues=[]; $tbgiteaissues=[];
$fgscrumupdate=false; $fgscrumupdate=false;
$this->datescrumupdate=new \DateTime(); $updatedates=[];
foreach($giteaissues as $giteaissue) { foreach($giteaissues as $giteaissue) {
// On ne prend pas les pull request
if(!is_null($giteaissue->pull_request)) {
continue;
}
$scrumissue=$this->_em->getRepository("App:Scrumissue")->findOneBy(["scrum"=>$scrum,"giteaid"=>$giteaissue->id]); $scrumissue=$this->_em->getRepository("App:Scrumissue")->findOneBy(["scrum"=>$scrum,"giteaid"=>$giteaissue->id]);
@ -139,8 +218,9 @@ class ScrumRepository extends ServiceEntityRepository
$scrumissue->setScrum($scrum); $scrumissue->setScrum($scrum);
$scrumissue->setRowid($lastrowid); $scrumissue->setRowid($lastrowid);
$scrumissue->setGiteaid($giteaissue->id); $scrumissue->setGiteaid($giteaissue->id);
$this->majissue($scrumissue,$giteaissue,true);
} }
else {
$fgissueupdated=false; $fgissueupdated=false;
if($scrumissue->getGiteajson()["updated_at"]!=json_decode(json_encode($giteaissue), true)["updated_at"]) if($scrumissue->getGiteajson()["updated_at"]!=json_decode(json_encode($giteaissue), true)["updated_at"])
$fgissueupdated=true; $fgissueupdated=true;
@ -156,39 +236,53 @@ class ScrumRepository extends ServiceEntityRepository
} }
if($fgissueupdated) { if($fgissueupdated) {
$updateissue=new \DateTime(json_decode(json_encode($giteaissue), true)["updated_at"]);
$updateissue->setTimezone(new \DateTimeZone("UTC"));
array_push($updatedates,$updateissue);
if($giteaissue->closed_at) {
$closeissue=new \DateTime(json_decode(json_encode($giteaissue), true)["closed_at"]);
$closeissue->setTimezone(new \DateTimeZone("UTC"));
array_push($updatedates,$closeissue);
}
$fgscrumupdate=true; $fgscrumupdate=true;
$this->majissue($scrumissue,$giteaissue); $this->majissue($scrumissue,$giteaissue,false);
}
} }
array_push($tbgiteaissues,$giteaissue->number); array_push($tbgiteaissues,$giteaissue->number);
} }
// Bug faudrait placer à closed l'ensemble des issues vu comme open mais qui ne le sont plus // Temps d'execution
if($viewclosed=="false") { $this->showtime(false,"Traiter les issues");
$scrumissues=$scrum->getScrumissues();
foreach($scrumissues as $scrumissue) {
if($scrumissue->getGiteastate()=="open"&&!in_array($scrumissue->getGiteanumber(),$tbgiteaissues)) {
$giteaissue=$this->giteaservice->getIssue($scrum->getGiteajson()["owner"]["login"],$scrum->getGiteajson()["name"],$scrumissue->getGiteanumber());
$fgscrumupdate=true;
$this->majissue($scrumissue,$giteaissue);
}
}
}
if($fgscrumupdate) { if($fgscrumupdate) {
$scrum->setUpdatedate($this->datescrumupdate); // Convertir les objets DateTime en timestamps
$this->_em->persist($scrum); $timestamps = array_map(function($date) {
return $date->getTimestamp();
}, $updatedates);
// Trouver le timestamp le plus récent
$mostRecentTimestamp = max($timestamps);
// Créer une nouvelle instance de DateTime à partir du timestamp le plus récent
$mostRecentDate = (new \DateTime())->setTimestamp($mostRecentTimestamp);
// Mettre à jour la date update du scrum
$scrum->setUpdatedate($mostRecentDate);
$this->_em->flush(); $this->_em->flush();
// Temps d'execution
$this->showtime(false,"Mettre à jour le scrum");
} }
} }
function majissue($scrumissue,$giteaissue) { public function majissue($scrumissue,$giteaissue,$issubmit) {
if($scrumissue->getGiteajson()["updated_at"]>$this->datescrumupdate)
$this->datescrumupdate=$scrumissue->getGiteajson()["updated_at"];
$scrumissue->setGiteanumber($giteaissue->number); $scrumissue->setGiteanumber($giteaissue->number);
$scrumissue->setGiteastate($giteaissue->state); $scrumissue->setGiteastate($giteaissue->state);
$scrumissue->setGiteatitle($giteaissue->title); $scrumissue->setGiteatitle($giteaissue->title);
if($giteaissue->milestone) { if($giteaissue->milestone) {
$scrumissue->setGiteamilestone($giteaissue->milestone->id); $scrumissue->setGiteamilestone($giteaissue->milestone->id);
$scrumissue->setGiteamilestonename($giteaissue->milestone->title); $scrumissue->setGiteamilestonename($giteaissue->milestone->title);
@ -199,6 +293,27 @@ class ScrumRepository extends ServiceEntityRepository
} }
$scrumissue->setGiteajson(json_decode(json_encode($giteaissue), true)); $scrumissue->setGiteajson(json_decode(json_encode($giteaissue), true));
// Si le ticket est lié à un sprint on s'assure que ce sprint est bien lié au milestone du ticket
if($scrumissue->getScrumsprint()) {
if($scrumissue->getScrumsprint()->getGiteamilestone()!=$scrumissue->getGiteamilestone()) {
$scrumissue->setScrumsprint(null);
}
}
$issueblock=$this->giteaservice->getissueblocks($scrumissue->getScrum()->getGiteajson()["owner"]["login"],$scrumissue->getScrum()->getGiteajson()["name"],$scrumissue->getGiteanumber());
if($issueblock&&!empty($issueblock)) {
$idblock=$issueblock[0]->number;
if(!$scrumissue->getScrumissueblock()||$scrumissue->getScrumissueblock()->getGiteanumber()!=$idblock) {
$scrumissueblock=$this->_em->getRepository('App:Scrumissue')->findOneBy(["scrum"=>$scrumissue->getScrum(),"giteanumber"=>$idblock]);
$scrumissue->setScrumissueblock($scrumissueblock);
}
}
elseif(empty($issueblock)&&$scrumissue->getScrumissueblock()) {
$scrumissue->setScrumissueblock(null);
}
$this->_em->persist($scrumissue); $this->_em->persist($scrumissue);
$this->_em->flush(); $this->_em->flush();
@ -223,4 +338,18 @@ class ScrumRepository extends ServiceEntityRepository
$this->_em->persist($scrumissue); $this->_em->persist($scrumissue);
$this->_em->flush(); $this->_em->flush();
} }
private function getMaxDate(\DateTime ...$dates): \DateTime {
// Initialiser la date maximale avec la première date de la liste
$maxDate = $dates[0];
// Comparer chaque date avec la date maximale actuelle
foreach ($dates as $date) {
if ($date > $maxDate) {
$maxDate = $date;
}
}
return $maxDate;
}
} }

View File

@ -33,14 +33,56 @@ class giteaService
$response=$this->api("POST",$apiurl,$body); $response=$this->api("POST",$apiurl,$body);
if(!$response||$response->code!="200") return false; if(!$response||$response->code!="200") return false;
else { else {
$this->session->set('giteatoken', $response->body->access_token); $this->session->set('giteatoken', $response->body->access_token);
$this->session->set('gitearefreshtoken', $response->body->refresh_token);
$date = new \DateTime();
$date->modify('+'.$response->body->expires_in.' seconds');
$this->session->set('gitearefreshdate',$date);
return $response->body->access_token; return $response->body->access_token;
} }
} }
public function refreshtoken() {
$apiurl = $this->params->get("oauthTokenurl");
$query= [
"client_id" => $this->params->get("oauthClientid"),
"client_secret" => $this->params->get("oauthClientsecret"),
"code" => $this->session->get("giteacode"),
"grant_type" => "refresh_token",
"refresh_token" => $this->session->get('gitearefreshtoken'),
];
$body = \Unirest\Request\Body::json($query);
$response=$this->api("POST",$apiurl,$body);
if(!$response||$response->code!="200") return false;
else {
$this->session->set('giteatoken', $response->body->access_token);
$this->session->set('gitearefreshtoken', $response->body->refresh_token);
$date = new \DateTime();
$date->modify('+'.$response->body->expires_in.' seconds');
$this->session->set('gitearefreshdate',$date);
return $response->body->access_token;
}
}
public function needrefresh() {
$date = new \DateTime();
// On refresh 15 minutes avant le terme
$refreshdate=clone $this->session->get('gitearefreshdate');
$refreshdate->modify('-900 seconds');
if($date>$refreshdate) return $this->refreshtoken();
else return $this->session->get('giteatoken');
}
public function deletetoken($username) { public function deletetoken($username) {
$apiurl=$this->url."/users/".$username."/tokens/".$this->session->get("giteatoken"); $apiurl=$this->url."/users/".$username."/tokens/".$this->session->get("giteatoken");
$response=$this->api("DELETE",$apiurl,null,$this->session->get("giteatoken")); $response=$this->api("DELETE",$apiurl,null,$this->session->get("giteatoken"));
@ -61,7 +103,7 @@ class giteaService
$response=$this->api("POST",$apiurl,$body,$this->session->get("giteatoken")); $response=$this->api("POST",$apiurl,$body,$this->session->get("giteatoken"));
if(!$response||$response->code!="200") return false; if(!$response||$response->code!="200") return false;
else { else {
$response->body= str_replace($this->giteaUrl,$this->giteaUrl.$context,$response->body); //$response->body= str_replace($this->giteaUrl,$this->giteaUrl.$context,$response->body);
return $response->body; return $response->body;
} }
} }
@ -181,6 +223,13 @@ class giteaService
else return $response->body; else return $response->body;
} }
public function getassignees($owner,$name) {
$apiurl = $this->url."/repos/$owner/$name/assignees";
$response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken"));
if(!$response||$response->code!="200") return false;
else return $response->body;
}
public function getcollaborators($owner,$name) { public function getcollaborators($owner,$name) {
$apiurl = $this->url."/repos/$owner/$name/collaborators"; $apiurl = $this->url."/repos/$owner/$name/collaborators";
$response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken")); $response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken"));
@ -202,9 +251,15 @@ class giteaService
else return $response->body; else return $response->body;
} }
public function getissues($owner,$name,$state="?state=open") { public function getmilestone($owner,$name,$id) {
$apiurl = $this->url."/repos/$owner/$name/issues".$state; $apiurl = $this->url."/repos/$owner/$name/milestones/".$id;
$response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken"));
if(!$response||$response->code!="200") return false;
else return $response->body;
}
public function getissues($owner,$name,$state="?state=open") {
$apiurl = $this->url."/repos/$owner/$name/issues".$state.($state==""?"?":"&")."type=issues";
$page=1; $page=1;
$limit=20; $limit=20;
$issues=[]; $issues=[];
@ -268,7 +323,6 @@ class giteaService
else return $response->body; else return $response->body;
} }
public function getissuetimelines($owner,$name,$index) { public function getissuetimelines($owner,$name,$index) {
$apiurl = $this->url."/repos/$owner/$name/issues/$index/timeline"; $apiurl = $this->url."/repos/$owner/$name/issues/$index/timeline";
$response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken")); $response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken"));
@ -276,13 +330,44 @@ class giteaService
else return $response->body; else return $response->body;
} }
public function getissueblocks($owner,$name,$index) {
$apiurl = $this->url."/repos/$owner/$name/issues/$index/blocks";
$response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken"));
if(!$response||$response->code!="200") return false;
else return $response->body;
}
public function postissueblocks($owner,$name,$index,$toblock) {
$apiurl = $this->url."/repos/$owner/$name/issues/$index/blocks";
$query= ["index" => $toblock,"owner" => $owner, "repo" => $name];
$body = \Unirest\Request\Body::json($query);
$body=str_replace('"'.$toblock.'"',$toblock,$body);
$response=$this->api("POST",$apiurl,$body,$this->session->get("giteatoken"));
if(!$response||($response->code!="200"&&$response->code!="201")) return false;
else return $response->body;
}
public function deleteissueblocks($owner,$name,$index,$toblock) {
$apiurl = $this->url."/repos/$owner/$name/issues/$index/blocks";
$query= ["index" => $toblock,"owner" => $owner, "repo" => $name];
$body = \Unirest\Request\Body::json($query);
$body=str_replace('"'.$toblock.'"',$toblock,$body);
$response=$this->api("DELETE",$apiurl,$body,$this->session->get("giteatoken"));
if(!$response||$response->code!="201") return false;
else return $response->body;
}
private function api($method,$url,$query,$token=null) { private function api($method,$url,$query,$token=null) {
// Entete // Entete
$headers = [ $headers = [
'Accept' => 'application/json', 'Accept' => 'application/json',
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
]; ];
if($token) $headers["Authorization"]="token ".$token;
if($token) {
$token=$this->needrefresh();
$headers["Authorization"]="token ".$token;
}
// Paramétrage unirest // Paramétrage unirest
\Unirest\Request::verifyPeer(false); \Unirest\Request::verifyPeer(false);

View File

@ -3,6 +3,7 @@ namespace App\Twig;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension class AppExtension extends AbstractExtension
{ {
@ -15,6 +16,13 @@ class AppExtension extends AbstractExtension
]; ];
} }
public function getFunctions()
{
return [
new TwigFunction('microtime', [$this, 'getMicrotime']),
];
}
public function urlAvatar($avatar) public function urlAvatar($avatar)
{ {
if(stripos($avatar,"http")===0) if(stripos($avatar,"http")===0)
@ -23,6 +31,11 @@ class AppExtension extends AbstractExtension
return $this->container->getParameter("appAlias")."uploads/avatar/".$avatar; return $this->container->getParameter("appAlias")."uploads/avatar/".$avatar;
} }
public function getMicrotime($asFloat = true)
{
return microtime($asFloat);
}
public function setContainer($container) public function setContainer($container)
{ {
$this->container = $container; $this->container = $container;

View File

@ -102,7 +102,7 @@
resizewidth=$('#largeimg').width(); resizewidth=$('#largeimg').width();
$('#largeimg').CropSelectJs({ $('#largeimg').CropSelectJs({
imageSrc: "/{{ appAlias }}/uploads/{{type}}/{{ file }}", imageSrc: "{{ asset("uploads/"~type~"/"~file) }}",
selectionResize: function(data) { resize(data); }, selectionResize: function(data) { resize(data); },
selectionMove: function(data) { move(data); }, selectionMove: function(data) { move(data); },
}); });

View File

@ -33,6 +33,8 @@ function ModalLoad(idmodal,title,path) {
} }
{% if wssuse %} {% if wssuse %}
console.log("pouet");
function subscribe(channeltype,channelkey,userkey) { function subscribe(channeltype,channelkey,userkey) {
console.log("== SUBSCRIBE "+channeltype+"-"+channelkey+" with userkey "+userkey); console.log("== SUBSCRIBE "+channeltype+"-"+channelkey+" with userkey "+userkey);
conn.send(JSON.stringify({ conn.send(JSON.stringify({

View File

@ -17,6 +17,9 @@
--colorfttitlelight-darker: {{ app.session.get('colorfttitlelight-darker')|raw }}; --colorfttitlelight-darker: {{ app.session.get('colorfttitlelight-darker')|raw }};
} }
.hidden {
display: none !important;
}
/* COLOR BODY */ /* COLOR BODY */
body { body {

View File

@ -45,7 +45,7 @@
{% for comment in issue.comments %} {% for comment in issue.comments %}
<h3 class='mt-3'>{{comment.user.login}} le {{comment.created_at|date("d/m/Y H:i")}}</h3> <h3 class='mt-3'>{{comment.user.login}} le {{comment.created_at|date("d/m/Y H:i")}}</h3>
<div class='card card-body'> <div class='card card-body'>
{{comment.body|markdown_to_html}} {{comment.body|raw}}
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -86,7 +86,7 @@
<div class='card card-body mb-3'> <div class='card card-body mb-3'>
{% for timeline in issue.timelines %} {% for timeline in issue.timelines %}
{% if timeline.type!="comment" and timeline.type!="project" and timeline.type!="added_deadline" and timeline.type!="modified_deadline"%} {% if timeline.type!="comment" and timeline.type!="project" and timeline.type!="added_deadline" and timeline.type!="modified_deadline" and timeline.type!="label" %}
<li><b>{{timeline.user.login}} le {{timeline.created_at|date("d/m/Y H:i")}}</b><br> <li><b>{{timeline.user.login}} le {{timeline.created_at|date("d/m/Y H:i")}}</b><br>
{% if timeline.type == "change_title" %} {% if timeline.type == "change_title" %}

View File

@ -0,0 +1,23 @@
{% extends "base.html.twig" %}
{% block body %}
{% set statut="" %}
{% set milestone="" %}
{% for issue in issues %}
{% if statut!=issue.statut %}
<p>&nbsp;</p>
<h2>{{issue.statut}}</h2>
{% set statut=issue.statut %}
{% set milestone="" %}
{% endif %}
{% if milestone!=issue.milestone %}
<p>&nbsp;</p>
<h3>{{issue.milestone}}</h3>
{% set milestone=issue.milestone %}
{% endif %}
<li>{{issue.id}} = {{ issue.title }}</li>
{% endfor %}
{% endblock %}

View File

@ -45,7 +45,7 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
<i class="fa fa-pencil-alt fa-fw"></i> Informations <i class="fa fa-pencil-alt fa-fw"></i> Informations
</div> </div>
@ -57,6 +57,26 @@
{{ form_row(form.users) }} {{ form_row(form.users) }}
</div> </div>
</div> </div>
{% if mode=="update" %}
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-pencil-alt fa-fw"></i> Sprints
<button id="addsprint" type="button" class="btn float-right fa fa-plus"></button>
<div class="custom-control custom-switch float-right">
<input type="checkbox" class="custom-control-input" id="viewsprintclosed">
<label class="custom-control-label" for="viewsprintclosed">Afficher les sprint clos</label>
</div>
</div>
<div id="scrumsprints" class="card-body">
<ol id="scrumsprints" class="list-group list-group-numbered">
</ol>
</div>
</div>
{% endif %}
</div> </div>
{% if mode=="update" %} {% if mode=="update" %}
@ -185,6 +205,23 @@
</div> </div>
</div> </div>
<div id="mymodalsprint" class="modal" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<iframe frameborder=0 width="100%" height="600px"></iframe>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block localjavascript %} {% block localjavascript %}
@ -196,6 +233,7 @@
loadscrumteams(); loadscrumteams();
loadscrumprioritys(); loadscrumprioritys();
loadscrumtypes(); loadscrumtypes();
loadscrumsprints();
{%endif%} {%endif%}
}); });
@ -376,6 +414,85 @@
}); });
} }
$("#addsprint").click(function() {
ModalLoad('mymodalsprint','Ajouter un sprint','{{path('app_scrumsprint_submit',{scrumid:scrum.id})}}');
});
$('#scrumsprints').on('click', '.modsprint', function(event) {
url="{{path('app_scrumsprint_update',{id:'xxx'})}}";
url=url.replace("xxx",$(this).data("id"));
ModalLoad('mymodalsprint','Modifier un sprint',url);
});
$('#mymodalsprint').on('hidden.bs.modal', function () {
loadscrumsprints();
});
$('#viewsprintclosed').change(function() {
if(this.checked) {
$(".sprintclosed").removeClass("hidden");
}
else {
$(".sprintclosed").addClass("hidden");
}
});
function loadscrumsprints() {
$("#scrumsprints").empty();
$.ajax({
method: "POST",
url: "{{path("app_scrumsprint_select",{scrumid:scrum.id})}}",
success: function(datas, dataType)
{
jQuery.each(datas, function(i, wid) {
style='';
classname='';
if(wid.closed) {
style='background-color:#cdcdcd;';
classname='sprintclosed';
if(!$("#viewsprintclosed").is(':checked')) {
classname+=' hidden';
}
}
html ='<li data-id="'+wid.id+'" class="list-group-item d-flex justify-content-between '+classname+'" style="'+style+'">';
html+='<div>';
html+='<div class="mr-3 p-2 d-inline-block"><i class="fas fa-arrows-alt-v fa-2x"></i></div>';
html+='<div class="d-inline-block">';
html+=wid.name;
html+='</div>';
html+='</div>';
html+='<button type="button" data-id="'+wid.id+'" class="modsprint btn float-right fa fa-file"></button>';
html+='</li>';
$("#scrumsprints").append(html);
});
$( "#scrumsprints" ).sortable({
axis: "y",
handle: ".fa-arrows-alt-v",
update: function( event, ui ) {
lstordered="";
$( "#scrumsprints li" ).each(function( index ) {
if(index==0) lstordered=$(this).data("id");
else lstordered=lstordered+","+$(this).data("id");
});
$.ajax({
method: "POST",
url: "{{path("app_scrumsprint_order",{scrumid:scrum.id})}}",
data: {
lstordered:lstordered
}
});
}
});
},
});
}
$("#addtype").click(function() { $("#addtype").click(function() {
ModalLoad('mymodaltype','Ajouter une colonne','{{path('app_scrumtype_submit',{scrumid:scrum.id})}}'); ModalLoad('mymodaltype','Ajouter une colonne','{{path('app_scrumtype_submit',{scrumid:scrum.id})}}');
}); });

View File

@ -0,0 +1,264 @@
{% extends "base.html.twig" %}
{% block localstyle %}
.issuecontent {
width:75%;
}
.issuedetail {
width:25%;
padding-right: 10px;
zoom:80%;
}
.text-small {
font-size:80%;
}
.card-header, .card-body {
padding:10px;
background-color: var(--colorbgbodylight-darker);
}
.issue-body {
zoom: 80%;
}
.badge {
color: white;
zoom:120%;
}
{% endblock %}
{% block body %}
<h3 id='issuetitle{{issue.number}}' class='issuetitle'>#{{issue.number}} = {{issue.title}}</h3>
<div id='issuediv{{issue.number}}' class='d-flex'>
<div class='issuecontent' class='d-flex flex-column'>
<div class="card mb-3">
<div class="card-header d-flex">
<div class="flex-grow-1">{{issue.user.login}}</div>
<div class="text-small"> le {{issue.created_at|date("d/m/Y H:i")}}</div>
</div>
<div class='card-body issue-body'>
{{issue.body|raw}}
</div>
</div>
{% if issue.comments %}
{% for comment in issue.comments %}
<div class="card mb-3">
<div class="card-header d-flex">
<div class="flex-grow-1">{{comment.user.login}}</div>
<div class="text-small"> le {{comment.created_at|date("d/m/Y H:i")}}</div>
</div>
<div class='card-body issue-body'>
{{comment.body|raw}}
</div>
</div>
{% endfor %}
{% endif %}
</div>
<div class='pl-3 issuedetail'>
<a target="_blank" class="btn btn-success w-100 mb-3" href="{{issue.html_url}}" style="zoom:120%">Modifier dans Gitea</a>
<div class="card mb-3">
<div class="modissu card-header d-flex" style="cursor:pointer">
<div class="flex-grow-1"><i class="fas fa-weight-hanging"></i></div>
<div>{{ issue.weight }}</div>
</div>
</div>
<div class='card mb-3'>
<div class="card-header">Statut</div>
<div class="card-body">
{% if issue.closed_at %}
Clos le {{issue.closed_at|date("d/m/Y H:i")}}
{% else %}
{{issue.statuslife}}
{% endif %}
</div>
</div>
<div class='card mb-3'>
<div class="card-header">Jalon</div>
<div class="card-body">
{% if issue.milestone %}
{{ issue.milestone.title|raw }}
{% else %}
Backlog
{% endif %}
</div>
</div>
{% if issue.sprint %}
<div class='card mb-3'>
<div class="card-header">Sprint</div>
<div class="card-body">
{{ issue.sprint }}
</div>
</div>
{% endif %}
{% if issue.refs %}
<div class='card mb-3'>
<div class="card-header">Références</div>
<div class='card-body mb-3'>
{% for histo in issue.refs %}
<div>
{{histo.label|raw}}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if issue.labels %}
<div class='card mb-3'>
<div class="card-header">Labels</div>
<div class='card-body mb-3'>
{% for label in issue.labels %}
<span class='badge mr-1 mb-1 p-2' style='background-color:#{{label.color}}'>{{label.name}}</span>
{% endfor %}
{% for histo in issue.labelhistos %}
<div class="mt-1">
<b>{{histo.user.login}} le {{histo.created_at|date("d/m/Y H:i")}}</b><br>
{{histo.label|raw}}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if issue.timelines %}
<div class='card mb-3'>
<div class="card-header">Historique</div>
<div class="card-body">
{% for timeline in issue.timelines %}
{% if timeline.type!="label" and timeline.type!="comment" and timeline.type!="project" and timeline.type!="added_deadline" and timeline.type!="modified_deadline"%}
<li><b>{{timeline.user.login}} le {{timeline.created_at|date("d/m/Y H:i")}}</b><br>
{% if timeline.type == "change_title" %}
<div class='pl-4'>Modification titre de <i>{{timeline.old_title}}</i> à <i>{{timeline.new_title}}</i></div>
{% elseif timeline.type == "milestone" %}
{% if timeline.milestone %}
<div class='pl-4'>Affectation au Jalon <i>{{timeline.milestone.title}}</i></div>
{% else %}
<div class='pl-4'>Suppression du Jalon</div>
{% endif %}
{% elseif timeline.type == "comment_ref" or timeline.type == "pull_ref" or timeline.type == "issue_ref" %}
<div class='pl-4'>Référencé le ticket <i>#{{timeline.ref_issue.number}} - {{timeline.ref_issue.title}}</i></div>
{% elseif timeline.type == "add_dependency" %}
<div class='pl-4'>Ajouté dépendance au ticket <i>#{{timeline.dependent_issue.number}} - {{timeline.dependent_issue.title}}</i></div>
{% elseif timeline.type == "remove_dependency" %}
<div class='pl-4'>Supprimé dépendance au ticket <i>#{{timeline.dependent_issue.number}} - {{timeline.dependent_issue.title}}</i></div>
{% elseif timeline.type == "pull_push" %}
<div class='pl-4'>Ajout révision</i></div>
{% elseif timeline.type == "assignees" %}
<div class='pl-4'>Affecté intervenant <i>{{timeline.assignee.login}}</i></div>
{% elseif timeline.type == "commit_ref" %}
<div class='pl-4'>Référencé depuis commit <i>#{{timeline.body|replace({'href="/':'target="_blank" href="'~giteaUrl~'/'})|raw}}</i></div>
{% elseif timeline.type == "merge_pull" %}
<div class='pl-4'>Révision fusionnée</div>
{% elseif timeline.type == "delete_branch" %}
<div class='pl-4'>Suppression branche {{timeline.old_ref|raw}}</div>
{% elseif timeline.type == "close" %}
<div class='pl-4'>Clos <i>Fermeture du ticket</i></div>
{% elseif timeline.type == "review" %}
<div class='pl-4'>Revue acceptée</div>
{% elseif timeline.type == "review_request" %}
<div class='pl-4'>Demande de révision</div>
{% elseif timeline.type == "reopen" %}
<div class='pl-4'>Réouverture du ticket</div>
{% else %}
{{ dump(timeline) }}
{%endif%}
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
<div id="mymodalissue" class="modal" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Estimation</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group ">
<label class="control-label required" for="issu_weight">
Poid
</label>
<input type="hidden" id="modal-issueid" name="modal-issueid" required="required" class=" form-control" value="">
<input type="number" placeholder="0.0" step="0.1" id="modal-issueweight" name="modal-issueweight" required="required" class=" form-control" value="">
</div>
<button id="issu_update" class="btn btn-success">Enregistrer</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block localjavascript %}
$(document).on('click','.modissu',function(){
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_info")}}",
data: {
id:{{issue.nineid}},
},
success: function(data) {
$("#modal-issueweight").val(data.weight);
$("#mymodalissue").modal('show');
$("#modal-issueweight").focus();
},
});
});
$("#issu_update").click(function(){
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_update")}}",
data: {
id:{{issue.nineid}},
weight:$("#modal-issueweight").val(),
},
success: function(data) {
parent.$(".issue{{issue.nineid}}-weight").html($("#modal-issueweight").val());
window.parent.refreshinfo();
location.reload();
},
error: function (request, status, error) {
$("#mymodalissue").modal('hide');
}
});
});
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends 'base.html.twig' %}
{% block body %}
<h1>{{scrum.name}}</h1>
<div class="form-group ">
<label class="control-label required" for="scrum_category">
Le ticket n°
<span class="mandatory">*</span>
</label>
<select id="issue" class="form-control">
<option value="" selected></option>
{% for issue in scrum.scrumissues %}
<option value="{{issue.id}}">#{{issue.giteanumber}} = {{issue.giteatitle}}</option>
{% endfor %}
</select>
</div>
<div class="form-group ">
<label class="control-label required" for="scrum_category">
bloque / débloque le ticket n°
<span class="mandatory">*</span>
</label>
<select id="block" class="form-control">
<option value="" selected></option>
{% for issue in scrum.scrumissues %}
<option value="{{issue.giteanumber}}">#{{issue.giteanumber}} = {{issue.giteatitle}}</option>
{% endfor %}
</select>
</div>
<button onClick="lock()" class="btn btn-success">Bloque</button>
<button onClick="unlock()" class="btn btn-success">Débloque</button>
{% endblock %}
{% block localjavascript %}
$(document).ready(function() {
$("#issue").select2();
$("#block").select2();
});
function lock() {
console.log($("#issue").val());
console.log($("#block").val());
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_block")}}",
data: {
id:$("#issue").val(),
issueblocked:$("#block").val(),
},
success: function(data) {
}
});
}
function unlock() {
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_unblock")}}",
data: {
id:$("#issue").val(),
},
success: function(data) {
}
});
}
{% endblock %}

View File

@ -5,10 +5,9 @@
{% block body %} {% block body %}
<div class="d-flex justify-content-start mt-3"> <div class="d-flex justify-content-start mt-3">
<div class="pr-2"> <div class="pr-2">
{% if app.user and is_granted('ROLE_MASTER') %} {% if app.user and is_granted('ROLE_MASTER') %}
<a class="btn btn-success btn-sm mt-2" href={{ path('app_scrum_submit') }}><i class="fa fa-plus mr-2"></i>Ajouter un Scrum</a> <a class="btn btn-success btn-sm d-flex" style="height:69px; justify-content: center; flex-direction: column" href={{ path('app_scrum_submit') }}><i class="fa fa-plus mr-2"></i>Ajouter un Scrum</a>
{% endif %} {% endif %}
</div> </div>
@ -47,7 +46,7 @@
<tr data-category="{{scrum.category}}" data-repo="{{scrum.giteaid}}"> <tr data-category="{{scrum.category}}" data-repo="{{scrum.giteaid}}">
<td> <td>
<a href="{{path('app_scrum_view',{id:scrum.id})}}"><i class="fas fa-columns fa-2x mr-1"></i></a> <a href="{{path('app_scrum_view',{id:scrum.id})}}"><i class="fas fa-columns fa-2x mr-1"></i></a>
<a href="{{path('app_issuescrum',{id:scrum.id})}}"><i class="fas fa-ticket-alt fa-2x mr-1"></i></a> <a href="{{path('app_scrum_table',{id:scrum.id})}}"><i class="fas fa-ticket-alt fa-2x mr-1"></i></a>
<a href="{{path('app_scrum_stat',{id:scrum.id})}}"><i class="fas fa-chart-area fa-2x mr-1"></i></a> <a href="{{path('app_scrum_stat',{id:scrum.id})}}"><i class="fas fa-chart-area fa-2x mr-1"></i></a>
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_MODO') or is_granted('ROLE_MASTER') %} {% if is_granted('ROLE_ADMIN') or is_granted('ROLE_MODO') or is_granted('ROLE_MASTER') %}

View File

@ -18,7 +18,7 @@
.flot-chart-subcontent { .flot-chart-subcontent {
height: 200px; height: 200px;
width: 40%; width: 350px;
} }
{% endblock %} {% endblock %}
@ -37,51 +37,78 @@
</select> </select>
</div> </div>
<div style="width:100%" class="mt-3"> <div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre EQUIPES</label> <label class="control-label" style="color:var(--colorftbodydark)">Filtre SPRINT</label>
<select id="filterteams" multiple="multiple" class="form-control"> <select id="filtersprints" multiple="multiple" class="form-control">
{% for team in scrum.scrumteams %} {% for sprint in sprints %}
<option value="{{team.giteaid}}">{{team.name}}</option> <option value="{{sprint.giteamilestone~"-"~sprint.id}}">{{sprint.giteamilestonename}}-{{sprint.name}}</option>
{% endfor %}
{% for giteamilestone in giteamilestones %}
<option value="{{giteamilestone.id~"--100"}}">{{giteamilestone.title}}-Aucun</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div style="width:100%" class="mt-3">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="filterbynumber">
<label class="custom-control-label" for="filterbynumber" style="color:var(--colorfttitledark);">Vue par {{ (filterbynumber=="true"?"nombre":"poids") }} de tickets</label>
</div>
</div>
</div> </div>
<div class="pl-3" style="width:100%;"> <div class="pl-3" style="width:100%;">
<div class="mt-4 mb-3" style="zoom:80%"> <div class="mt-4 mb-3" style="zoom:80%">
<button class="btn btn-success" onClick="showFilters()"><i class="fas fa-filter"></i></button> <button class="btn btn-success" onClick="showFilters()"><i class="fas fa-filter"></i></button>
<a class="btn btn-success" href="{{path('app_scrum_view',{id:scrum.id})}}"><i class="fas fa-columns"></i></a> <a class="btn btn-success" href="{{path('app_scrum_view',{id:scrum.id})}}"><i class="fas fa-columns"></i></a>
<a class="btn btn-success" href="{{path('app_issuescrum',{id:scrum.id})}}"><i class="fas fa-ticket-alt"></i></a> <a class="btn btn-success" href="{{path('app_scrum_table',{id:scrum.id})}}"><i class="fas fa-ticket-alt"></i></a>
<span id="textfilters"></span> <span id="textfilters"></span>
</div> </div>
<h1>{{ scrum.name }}</h1> <h1>{{ scrum.name }}</h1>
<div class="d-flex flex-column mt-4"> <div id="tbestim" class="d-flex flex-column mt-4">
{% for milestone in tbstat %}
<div class="flot-chart mr-4" data-milestone="{{ milestone.id }}">
<h4>{{milestone.name}}</h4>
<div class="mt-4">
<div id="floatdonut{{ milestone.id }}" class="flot-chart-content" style="float:left"></div>
<div class="mt-3 pb-3" style="width:100%">
<div class="d-flex justify-content-between mt-4"> {% for jalon in tbestim %}
{% for column in milestone.stat %} {% if jalon.nbjrs>0 %}
<div style="width:250px"> <div class="card mb-4" data-milestone="{{ jalon.idjal }}">
<div style="background-color:{{column.color}}; padding:3px;margin-bottom:3px;">{{ column.label }} = {{ column.total }}</div> <div class="card-header">{{jalon.nmjal}}</div>
<small> <div class="card-body d-flex justify-content-center">
<div style="line-height:15px; width:100%"> <div class="d-flex flex-column">
{% for label in column.labels %} <div class="ustify-content-center" style="display:flex" data-graphmilestone="{{ jalon.idjal }}">
<li >{{label.label}} = {{label.total}}</li> <div id="floatdonut{{ jalon.idjal }}" class="flot-chart-content"></div>
<div style="min-width:250px; color: #ffffff; padding: 3px;">
{% for column in jalon.columns %}
<div style="padding: 3px; background-color:#{{column.color}}">{{column.nmcol}}<span class="float-right">{{(filterbynumber=="true"?column.nbiss:column.nbjrs)}} = {{((filterbynumber=="true"?column.nbiss:column.nbjrs) * 100 / (filterbynumber=="true"?jalon.nbiss:jalon.nbjrs))|number_format}}%</<span></div>
{% endfor %}
<div style="padding: 3px;color: #000">TOTAL<span class="float-right">{{(filterbynumber=="true"?jalon.nbiss:jalon.nbjrs)}} = 100%</<span></div>
</div>
</div>
<div class="d-flex flex-wrap justify-content-center">
{% for sprint in jalon.sprints %}
{% if jalon.nbjrs != sprint.nbjrs and sprint.nbjrs>0 %}
<div class="card mt-4 mr-4" data-sprint="{{ jalon.idjal~"-"~sprint.idspr }}">
<div class="card-header">{{sprint.nmspr}}</div>
<div class="card-body d-flex flex-column">
<div id="floatdonut{{ jalon.idjal~"-"~sprint.idspr }}" class="flot-chart-subcontent" style="float:left"></div>
<div class="mt-3" style="color: #ffffff;">
{% for column in sprint.columns %}
<div style="padding: 3px; background-color:#{{column.color}}">{{column.nmcol}}<span class="float-right">{{(filterbynumber=="true"?column.nbiss:column.nbjrs)}} = {{((filterbynumber=="true"?column.nbiss:column.nbjrs) * 100 / (filterbynumber=="true"?sprint.nbiss:sprint.nbjrs))|number_format}}%</<span></div>
{% endfor %}
<div style="padding: 3px;color: #000">TOTAL<span class="float-right">{{(filterbynumber=="true"?sprint.nbiss:sprint.nbjrs)}} = 100%</<span></div>
</div>
</div>
</div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
</small>
</div>
{%endfor%}
</div>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -90,6 +117,25 @@
{% endblock %} {% endblock %}
{% block localjavascript %} {% block localjavascript %}
// View par nombre de tickets
{% if filterbynumber %}
$("#filterbynumber").prop( "checked", {{ filterbynumber }} )
{% endif %}
$('#filterbynumber').change(function() {
$.ajax({
method: "POST",
url: "{{ path('app_user_preference') }}",
data: {
key:'filterbynumber',
id:{{scrum.id}},
value: this.checked
}
}).done(function( data ) {
location.reload();
});
});
function showFilters() { function showFilters() {
if($("#filters").hasClass("d-flex")) { if($("#filters").hasClass("d-flex")) {
toshow=0; toshow=0;
@ -116,13 +162,28 @@
$(document).ready(function() { $(document).ready(function() {
// Apply Filter // Apply Filter
function showhide() { function showhide() {
// Afficher masquer les jalons
if($("#filtermilestones").val().length !== 0) { if($("#filtermilestones").val().length !== 0) {
$("[data-milestone]").hide(); $("[data-milestone]").hide();
$.each($("#filtermilestones").val(), function( index, value ) { $.each($("#filtermilestones").val(), function( index, value ) {
$("[data-milestone="+value+"]").show(); $("[data-milestone="+value+"]").show();
$("[data-graphmilestone="+value+"]").show();
}); });
} }
else $("[data-milestone]").show(); else {
$("[data-milestone]").show();
$("[data-graphmilestone]").show();
}
// Afficher masquer les sprints
if($("#filtersprints").val().length !== 0) {
$("[data-sprint]").hide();
$("[data-graphmilestone]").hide();
$.each($("#filtersprints").val(), function( index, value ) {
$("[data-sprint="+value+"]").show();
});
}
else if($("#filtermilestones").val().length === 0)$("[data-sprint]").show();
textfilters=""; textfilters="";
if($("#filtermilestones").val().length!==0) { if($("#filtermilestones").val().length!==0) {
@ -134,10 +195,10 @@
}); });
} }
if($("#filterteams").val().length!==0) { if($("#filtersprints").val().length!==0) {
data = $("#filterteams").select2('data'); data = $("#filtersprints").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>EQUIPES</b> ="; textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>SPRINT</b> =";
$.each($("#filterteams").val(), function( index, value ) { $.each($("#filtersprints").val(), function( index, value ) {
if(index>0)textfilters=textfilters+" &"; if(index>0)textfilters=textfilters+" &";
textfilters=textfilters+" "+data[index].text; textfilters=textfilters+" "+data[index].text;
}); });
@ -175,46 +236,47 @@
filtermilestones(); filtermilestones();
}); });
// Filter Teams // Filter Sprints
function filterteams() { function filtersprints() {
$.ajax({ $.ajax({
method: "POST", method: "POST",
url: "{{ path('app_user_preference') }}", url: "{{ path('app_user_preference') }}",
data: { data: {
key:'filterteams', key:'filtersprints',
id:{{scrum.id}}, id:{{scrum.id}},
value: $("#filterteams").val() value: $("#filtersprints").val()
} }
}); });
location.reload(); showhide();
} }
$('#filterteams').select2();
{% if filterteams %} $('#filtersprints').select2();
{% for team in filterteams %} {% if filtersprints %}
$("#filterteams").val($("#filterteams").val().concat("{{team}}")); {% for sprint in filtersprints %}
$("#filtersprints").val($("#filtersprints").val().concat("{{sprint}}"));
{%endfor%} {%endfor%}
$('#filterteams').trigger('change'); $('#filtersprints').trigger('change');
{% endif %} {% endif %}
$('#filterteams').on("select2:select", function(e) { $('#filtersprints').on("select2:select", function(e) {
filterteams(); filtersprints();
}); });
$('#filterteams').on("select2:unselect", function(e) { $('#filtersprints').on("select2:unselect", function(e) {
filterteams(); filtersprints();
}); });
{% for milestone in tbstat %} {% for jalon in tbestim %}
var data = [ var data = [
{% for data in milestone.stat %} {% for column in jalon.columns %}
{ {
label: "{{ data.label}}", label: "{{ column.nmcol}}",
data: {{ data.total }}, data: {{ column.nbiss }},
color: "{{ data.color }}", color: "#{{ column.color }}",
}, },
{% endfor %} {% endfor %}
]; ];
var plotObj = $.plot($("#floatdonut{{ milestone.id }}"), data, { var plotObj = $.plot($("#floatdonut{{ jalon.idjal }}"), data, {
series: { series: {
pie: { pie: {
show: true, show: true,
@ -238,29 +300,39 @@
}, },
}); });
{% for data in milestone.stat %} console.log("BY number = {{ (filterbynumber=="true" ? 'filtre par nbticket':'filtre par poids') }}");
{% for data in milestone.stat %} {% for sprint in jalon.sprints %}
console.log("Sprint = {{sprint.nmspr}}");
{% for column in sprint.columns %}
console.log({{ (filterbynumber=="true" ? column.nbiss:column.nbjrs) }});
{% endfor %}
var data = [ var data = [
{% for label in data.labels %} {% for column in sprint.columns %}
{ {
label: "{{ label.label}}", label: "{{ column.nmcol}}",
data: {{ label.total }}, data: {{ (filterbynumber=="true" ? column.nbiss:column.nbjrs) }},
color: "{{ label.color }}", color: "#{{ column.color }}",
}, },
{% endfor %} {% endfor %}
]; ];
var plotObj = $.plot($("#floatsubdonut{{ milestone.id }}-{{ data.id }}"), data, {
var plotObj = $.plot($("#floatdonut{{ jalon.idjal~"-"~sprint.idspr }}"), data, {
series: { series: {
pie: { pie: {
show: true, show: true,
radius: 1,
label: { label: {
show: false, show: true,
radius: 3/4, radius: 1,
threshold: 0.1, threshold: 0.1,
background: { background: {
opacity: 0.5, opacity: 0.5,
color: '#cdcdcd', color: '#cdcdcd',
}, },
formatter: function(label, series) {
return '<span style="color:#000; padding:3px;">' + label + '</span>';
}
} }
} }
}, },
@ -270,7 +342,6 @@
}); });
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endfor %}
{% if not showfilters %} {% if not showfilters %}
$("#filters").addClass("d-none"); $("#filters").addClass("d-none");

View File

@ -32,31 +32,15 @@
{% block body %} {% block body %}
<div class="d-flex"> <div class="d-flex">
<div id="filters" class="d-flex flex-column pl-2 pr-2 " style="width:350px; background-color:var(--colorbgbodydark);min-height:1500px;"> <div id="filters" class="d-flex flex-column pl-2 pr-2 " style="width:350px; background-color:var(--colorbgbodydark);min-height:1500px;">
{% if fgpoker %}
<div id="online" class="mt-2"></div>
{% endif %}
<div style="width:100%" class="mt-3"> <div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre TICKET</label> <label class="control-label" style="color:var(--colorftbodydark)">Filtre TICKET</label>
<input type="number" id="filterticket" class=" form-control"> <input type="number" id="filterticket" class=" form-control">
</div> </div>
{% if id == 0 %}
<div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre CATEGORIES</label>
<select id="filtercategorys" multiple="multiple" class="form-control">
{% for giteacategory in giteacategorys %}
<option value="{{giteacategory}}">{{giteacategory}}</option>
{% endfor %}
</select>
</div>
<div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre PROJETS</label>
<select id="filterrepos" multiple="multiple" class="form-control">
{% for gitearepo in gitearepos %}
<option value="{{gitearepo.id}}">{{gitearepo.full_name}}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div style="width:100%" class="mt-3"> <div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre JALONS</label> <label class="control-label" style="color:var(--colorftbodydark)">Filtre JALONS</label>
<select id="filtermilestones" multiple="multiple" class="form-control"> <select id="filtermilestones" multiple="multiple" class="form-control">
@ -66,6 +50,18 @@
</select> </select>
</div> </div>
<div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre SPRINT</label>
<select id="filtersprints" multiple="multiple" class="form-control">
{% for sprint in sprints %}
<option value="{{sprint.giteamilestone~"-"~sprint.id}}">{{sprint.giteamilestonename}}-{{sprint.name}}</option>
{% endfor %}
{% for giteamilestone in giteamilestones %}
<option value="{{giteamilestone.id~"--100"}}">{{giteamilestone.title}}-Aucun</option>
{% endfor %}
</select>
</div>
<div style="width:100%" class="mt-3"> <div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre STATUTS</label> <label class="control-label" style="color:var(--colorftbodydark)">Filtre STATUTS</label>
<select id="filtercolumns" multiple="multiple" class="form-control"> <select id="filtercolumns" multiple="multiple" class="form-control">
@ -143,7 +139,7 @@
<a class="btn btn-success" href="{{path('app_scrum_stat',{id:id})}}"><i class="fas fa-chart-area"></i></a> <a class="btn btn-success" href="{{path('app_scrum_stat',{id:id})}}"><i class="fas fa-chart-area"></i></a>
{% endif %} {% endif %}
<span id="textfilters"></span> <span id="textfilters"></span>
<a class="btn btn-success float-right" href="{{path('app_issuescrum',{id:id,'fgcsv':true})}}">Export CSV</a> <a class="btn btn-success float-right" href="{{path('app_scrum_table',{id:id,'fgcsv':true})}}">Export CSV</a>
</div> </div>
@ -153,15 +149,21 @@
<th class="no-sort"></th> <th class="no-sort"></th>
{%if id==0 %}<th style="width:100px">Projet</th>{%endif%} {%if id==0 %}<th style="width:100px">Projet</th>{%endif%}
<th style="width:200px">Jalon</th> <th style="width:200px">Jalon</th>
<th style="width:200px">Sprint</th>
<th style="width:200px">Type</th> <th style="width:200px">Type</th>
<th style="width:70px"class="no-string">N°</th> <th style="width:70px"class="no-string">N°</th>
<th style="width:1000px">Titre</th> <th style="width:1000px">Titre</th>
{% if (not wssuse or not fgpoker) %}
<th style="width:200px">Equipe</th> <th style="width:200px">Equipe</th>
<th style="width:250px">Priorité</th> <th style="width:250px">Priorité</th>
<th style="width:70px">Poid</th>
<th style="width:135px">Affecté à</th> <th style="width:135px">Affecté à</th>
<th style="width:135px">Statut</th>
<th style="width:300px">Etiquettes</th> <th style="width:300px">Etiquettes</th>
{% else %}
<th style="width:300px">Poker</th>
{% endif %}
<th style="width:135px">Statut</th>
<th style="width:70px">Poid</th>
</tr> </tr>
</thead> </thead>
@ -182,19 +184,15 @@
{% if tosee %} {% if tosee %}
{% set dataticket = giteaissue.number %} {% set dataticket = giteaissue.number %}
{% set datarepo = gitearepo.id %} {% set datarepo = gitearepo.id %}
{% set datamilestone = (giteaissue.milestone?giteaissue.milestone.id:-100) %}
{% if giteaissue.milestone %} {% set datasprint = datamilestone~"-"~giteaissue.sprintid %}
{% set datamilestone = giteaissue.milestone.id %}
{%else%}
{% set datamilestone = gitearepo.full_name %}
{%endif%}
{% set statut = "Backlog" %} {% set statut = "Backlog" %}
{% set teams = "" %} {% set teams = "" %}
{% set types = "" %} {% set types = "" %}
{% set datateams = "" %} {% set datateams = "" %}
{% set datatypes = "" %} {% set datatypes = "" %}
{% set prioritys = '<span class="btn-link tag mr-1" style="background-color:#70c24a"><i class="fas fa-tag"></i>'~giteaprioritys|last~'</span>' %} {% set prioritys = (not giteaprioritys?'':'<span class="btn-link tag mr-1" style="background-color:#70c24a"><i class="fas fa-tag"></i>'~giteaprioritys|last~'</span>') %}
{% set dataprioritys = ','~giteaprioritys|last %} {% set dataprioritys = ','~giteaprioritys|last %}
{% set labels = "" %} {% set labels = "" %}
{% set datalabels = "" %} {% set datalabels = "" %}
@ -221,12 +219,13 @@
{% set dataassignees=dataassignees~','~assignee.username %} {% set dataassignees=dataassignees~','~assignee.username %}
{% endfor %} {% endfor %}
<tr data-category="{{gitearepo.category}}" data-repo="{{datarepo}}" data-milestone="{{datamilestone}}" data-ticket="{{dataticket}}" data-column="{{statut}}" data-teams="{{datateams}}" data-prioritys="{{dataprioritys}}" data-types="{{datatypes}}" data-labels="{{datalabels}}" data-assignees="{{dataassignees}}"> <tr data-id="{{giteaissue.issueid}}" data-milestone="{{datamilestone}}" data-sprint="{{datasprint}}" data-ticket="{{dataticket}}" data-column="{{statut}}" data-teams="{{datateams}}" data-prioritys="{{dataprioritys}}" data-types="{{datatypes}}" data-labels="{{datalabels}}" data-assignees="{{dataassignees}}">
<td> <td>
<a target="_blank" class="btn btn-link fa fa-file" href="{{giteaissue.html_url}}"></a> <a target="_blank" class="btn btn-link fa fa-file" href="{{giteaissue.html_url}}"></a>
</td> </td>
{%if id==0 %}<td>{{ gitearepo.full_name }}</td>{%endif%} {%if id==0 %}<td>{{ gitearepo.full_name }}</td>{%endif%}
<td>{% if giteaissue.milestone %}{{ giteaissue.milestone.title }} {%endif%}</td> <td>{% if giteaissue.milestone %}{{ giteaissue.milestone.title }} {%endif%}</td>
<td>{% if giteaissue.sprintid %}{{ giteaissue.sprintname }} {%endif%}</td>
<td>{{ types|raw }}</td> <td>{{ types|raw }}</td>
<td>{{ giteaissue.number }}</td> <td>{{ giteaissue.number }}</td>
<td> <td>
@ -235,15 +234,11 @@
{{ giteaissue.title }} {{ giteaissue.title }}
</a> </a>
</td> </td>
{% if (not wssuse or not fgpoker) %}
<td>{{ teams|raw }}</td> <td>{{ teams|raw }}</td>
<td>{{ prioritys|raw }}</td> <td>{{ prioritys|raw }}</td>
<td>
<div id="modissu{{ giteaissue.issueid }}" data-issue="{{ giteaissue.issueid }}" data-giteaid="{{giteaissue.number}}" data-giteatitle="{{ giteaissue.title }}" type="button" class="modissu btn btn-link">
<i class="fas fa-weight-hanging"></i> = <span id="issue{{giteaissue.issueid}}-weight">{{ giteaissue.weight }}</span>
</div>
</td>
{% set dataorder="" %} {% set dataorder="" %}
{% for assignee in giteaissue.assignees %} {% for assignee in giteaissue.assignees %}
{% set dataorder=dataorder~assignee.username %} {% set dataorder=dataorder~assignee.username %}
@ -254,8 +249,19 @@
<img src="{{assignee.avatar_url}}" class="assignee" title="{{assignee.username}}"> <img src="{{assignee.avatar_url}}" class="assignee" title="{{assignee.username}}">
{% endfor %} {% endfor %}
</td> </td>
<td>{{ statut }}</td>
<td>{{ labels|raw }}</td> <td>{{ labels|raw }}</td>
{% else %}
<td>
<div class="pokertd d-flex"></div>
</td>
{% endif %}
<td>{{ statut }}</td>
<td>
<div id="modissu{{ giteaissue.issueid }}" data-issue="{{ giteaissue.issueid }}" data-giteaid="{{giteaissue.number}}" data-giteatitle="{{ giteaissue.title }}" type="button" class="modissu btn btn-link">
<i class="fas fa-weight-hanging"></i> = <span id="issue{{giteaissue.issueid}}-weight">{{ giteaissue.weight }}</span>
</div>
</td>
</tr> </tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -320,9 +326,8 @@
function showhide() { function showhide() {
textfilters=""; textfilters="";
ticketfilter=$("#filterticket").val(); ticketfilter=$("#filterticket").val();
categoryfilters=$("#filtercategorys").val();
repofilters=$("#filterrepos").val();
milestonefilters=$("#filtermilestones").val(); milestonefilters=$("#filtermilestones").val();
sprintfilters=$("#filtersprints").val();
columnfilters=$("#filtercolumns").val(); columnfilters=$("#filtercolumns").val();
teamfilters=$("#filterteams").val(); teamfilters=$("#filterteams").val();
priorityfilters=$("#filterprioritys").val(); priorityfilters=$("#filterprioritys").val();
@ -340,6 +345,7 @@
{% endif %} {% endif %}
ticket= $(el).data('ticket'); ticket= $(el).data('ticket');
milestone = $(el).data('milestone'); milestone = $(el).data('milestone');
sprint = $(el).data('sprint');
column = $(el).data('column'); column = $(el).data('column');
teams = $(el).data('teams').split(','); teams = $(el).data('teams').split(',');
prioritys = $(el).data('prioritys').split(','); prioritys = $(el).data('prioritys').split(',');
@ -373,6 +379,11 @@
toreturn=false; toreturn=false;
} }
if(sprintfilters.length!==0 && jQuery.inArray(sprint.toString(), sprintfilters )<0) {
toreturn=false;
}
if(columnfilters.length!==0 && jQuery.inArray(column.toString(), columnfilters )<0) { if(columnfilters.length!==0 && jQuery.inArray(column.toString(), columnfilters )<0) {
toreturn=false; toreturn=false;
} }
@ -439,26 +450,6 @@
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>TICKET</b> ="+ticketfilter; textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>TICKET</b> ="+ticketfilter;
} }
{% if id == 0 %}
if(categoryfilters.length!==0) {
data = $("#filtercategorys").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>CATEGORIES</b> =";
$.each(categoryfilters, function( index, value ) {
if(index>0)textfilters=textfilters+" &";
textfilters=textfilters+" "+data[index].text;
});
}
if(repofilters.length!==0) {
data = $("#filterrepos").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>PROJETS</b> =";
$.each(repofilters, function( index, value ) {
if(index>0)textfilters=textfilters+" &";
textfilters=textfilters+" "+data[index].text;
});
}
{% endif %}
if(milestonefilters.length!==0) { if(milestonefilters.length!==0) {
data = $("#filtermilestones").select2('data'); data = $("#filtermilestones").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>JALONS</b> ="; textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>JALONS</b> =";
@ -468,6 +459,15 @@
}); });
} }
if(sprintfilters.length!==0) {
data = $("#filtersprints").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>SPRINTS</b> =";
$.each(sprintfilters, function( index, value ) {
if(index>0)textfilters=textfilters+" &";
textfilters=textfilters+" "+data[index].text;
});
}
if(columnfilters.length!==0) { if(columnfilters.length!==0) {
data = $("#filtercolumns").select2('data'); data = $("#filtercolumns").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>STATUS</b> ="; textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>STATUS</b> =";
@ -537,36 +537,6 @@
table.draw(); table.draw();
} }
{% if id == 0 %}
function filtercategorys() {
$.ajax({
method: "POST",
url: "{{ path('app_user_preference') }}",
data: {
key:'filtercategorys',
id:{{id}},
value: $("#filtercategorys").val()
}
});
showhide();
}
function filterrepos() {
$.ajax({
method: "POST",
url: "{{ path('app_user_preference') }}",
data: {
key:'filterrepos',
id:{{id}},
value: $("#filterrepos").val()
}
});
showhide();
}
{% endif %}
function filtermilestones() { function filtermilestones() {
$.ajax({ $.ajax({
method: "POST", method: "POST",
@ -581,6 +551,20 @@
showhide(); showhide();
} }
function filtersprints() {
$.ajax({
method: "POST",
url: "{{ path('app_user_preference') }}",
data: {
key:'filtersprints',
id:{{id}},
value: $("#filtersprints").val()
}
});
showhide();
}
function filtercolumns() { function filtercolumns() {
$.ajax({ $.ajax({
method: "POST", method: "POST",
@ -732,36 +716,6 @@
}); });
$(document).ready(function() { $(document).ready(function() {
{% if id== 0 %}
$('#filtercategorys').select2();
{% if filtercategorys %}
{% for category in filtercategorys %}
$("#filtercategorys").val($("#filtercategorys").val().concat("{{category}}"));
{%endfor%}
$('#filtercategorys').trigger('change');
{% endif %}
$('#filtercategorys').on("select2:select", function(e) {
filtercategorys();
});
$('#filtercategorys').on("select2:unselect", function(e) {
filtercategorys();
});
$('#filterrepos').select2();
{% if filterrepos %}
{% for repo in filterrepos %}
$("#filterrepos").val($("#filterrepos").val().concat("{{repo}}"));
{%endfor%}
$('#filterrepos').trigger('change');
{% endif %}
$('#filterrepos').on("select2:select", function(e) {
filterrepos();
});
$('#filterrepos').on("select2:unselect", function(e) {
filterrepos();
});
{% endif %}
$("#filterticket").on("keyup", function() { $("#filterticket").on("keyup", function() {
showhide(); showhide();
}); });
@ -780,6 +734,20 @@
filtermilestones(); filtermilestones();
}); });
$('#filtersprints').select2();
{% if filtersprints %}
{% for sprint in filtersprints %}
$("#filtersprints").val($("#filtersprints").val().concat("{{sprint}}"));
{%endfor%}
$('#filtersprints').trigger('change');
{% endif %}
$('#filtersprints').on("select2:select", function(e) {
filtersprints();
});
$('#filtersprints').on("select2:unselect", function(e) {
filtersprints();
});
$('#filtercolumns').select2(); $('#filtercolumns').select2();
{% if filtercolumns %} {% if filtercolumns %}
{% for column in filtercolumns %} {% for column in filtercolumns %}
@ -894,4 +862,89 @@
$("#mycontent").show(); $("#mycontent").show();
}); });
{% if wssuse and fgpoker %}
var conn;
function connect() {
conn = new WebSocket("{{wssurl}}");
conn.onopen = function(e) {
console.log("== CONNECT");
{% set userkey = "" %}
{% if app.user %}
{% set userkey = app.user.apikey %}
{%endif%}
subscribe("home",{{id}},"{{userkey}}");
sendMessage({command: "alive"});
};
conn.onmessage = function(e) {
ret=JSON.parse(e.data);
console.log("MESSAGE REU ="+ret.command);
switch(ret.command) {
case "alive" :
if(!$('#online'+ret.from.id).length) {
img='<img id="online'+ret.from.id+'" src="'+ret.from.avatar+'" class="avatar ml-2 mr-2" title="'+ret.from.displayname+'">';
$("#online").append(img);
$("tr").each(function(){
line=$(this);
issueid=$(this).data("id");
url="{{path("app_poker_get",{userid:"xxx",issueid:"yyy"})}}";
url=url.replace("xxx",ret.from.id);
url=url.replace("yyy",issueid);
$.ajax({
method: "POST",
url: url,
async: false,
success: function(data) {
html ='<div class="d-flex flex-column align-items-center" data-user="'+ret.from.id+'">';
html+='<img src="'+ret.from.avatar+'" class="avatar mb-1" style="zoom:80%;" title="'+ret.from.displayname+'">';
html+='<input class="pokervalue" id="poker-'+ret.from.id+'-'+issueid+'" data-user="'+ret.from.id+'" data-issue="'+issueid+'" type="number" style="width:60px" value="'+data+'"></input>';
html+='</div>';
line.find(".pokertd").append(html);
},
});
})
}
if(ret.from.id!={{app.user.id}}) {
console.log("meto");
sendMessage({command: "meto"});
}
break;
case "adead" :
$('#online'+ret.from.id).remove();
$('[data-user='+ret.from.id+']').remove();
break;
case "meto" :
if(!$('#online'+ret.from.id).length) {
html='<img id="online'+ret.from.id+'" src="'+ret.from.avatar+'" class="avatar ml-2 mr-2" title="'+ret.from.displayname+'">';
$("#online").append(html);
$(".pokertd").append(html);
}
break;
}
};
conn.onclose = function(e) {
console.log("== DISCONNECT");
$('#online img').remove();
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
setTimeout(function() { connect(); }, 1000);
};
}
connect();
$("body").on("input", ".pokervalue", function () {
console.log($(this).val());
});
{% endif %}
{% endblock %} {% endblock %}

View File

@ -22,19 +22,24 @@
.tag { .tag {
border-radius: 5px; border-radius: 5px;
padding: 8px 8px; padding: 4px 8px;
margin-bottom: 5px; //margin-bottom: 2px;
display: inline-block; display: inline-block;
min-width: 35px; min-width: 35px;
text-align: center; text-align: center;
color: #ffffff !important; color: #ffffff !important;
zoom: 80%; zoom: 70%;
} }
.tag i { .tag i {
margin-right:5px; margin-right:5px;
} }
.tooltip-inner {
text-align: left !important; /* Aligne le texte à gauche */
white-space: pre-line; /* Conserve les retours à la ligne */
}
.assignee { .assignee {
width:30px; width:30px;
margin: 5px 5px 0px 0px; margin: 5px 5px 0px 0px;
@ -43,9 +48,19 @@
.state-closed { .state-closed {
background-color: #cdcdcd !important; background-color: #cdcdcd !important;
} }
.text-small { font-size:80%}
.text-verysmall { font-size:70%}
.submenu a:hover {
text-decoration: none;
color: var(--colorfttitlelight);
}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% set start = microtime(true) %}
<div class="d-flex"> <div class="d-flex">
<div id="filters" class="d-flex flex-column pl-2 pr-2 " style="width:350px; background-color:var(--colorbgbodydark);min-height:1500px;"> <div id="filters" class="d-flex flex-column pl-2 pr-2 " style="width:350px; background-color:var(--colorbgbodydark);min-height:1500px;">
<div style="width:100%" class="mt-3"> <div style="width:100%" class="mt-3">
@ -58,6 +73,18 @@
</select> </select>
</div> </div>
<div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre SPRINT</label>
<select id="filtersprints" multiple="multiple" class="form-control">
{% for sprint in sprints %}
<option value="{{sprint.giteamilestone~"-"~sprint.id}}">{{sprint.giteamilestonename}}-{{sprint.name}}</option>
{% endfor %}
{% for giteamilestone in giteamilestones %}
<option value="{{giteamilestone.id~"--100"}}">{{giteamilestone.title}}-Aucun</option>
{% endfor %}
</select>
</div>
<div style="width:100%" class="mt-3"> <div style="width:100%" class="mt-3">
<label class="control-label" style="color:var(--colorftbodydark)">Filtre TYPES</label> <label class="control-label" style="color:var(--colorftbodydark)">Filtre TYPES</label>
<select id="filtertypes" multiple="multiple" class="form-control"> <select id="filtertypes" multiple="multiple" class="form-control">
@ -113,12 +140,14 @@
</select> </select>
</div> </div>
<div style="width:100%" class="mt-3">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="viewcondensed">
<label class="custom-control-label" for="viewcondensed" style="color:var(--colorfttitledark);">Vue condensée</label>
</div>
</div>
<div style="color:var(--colorftbodydark);zoom:75%;" class="mt-5"> <div id="tbestim" class="mt-5">
{% for giteamilestone in giteamilestones %}
{{giteamilestone.title}} = <span id="total{{giteamilestone.id}}" class="totalweight">0</span><br>
{% endfor %}
Aucun = <span id="total-100" class="totalweight">0</span><br>
</div> </div>
</div> </div>
@ -130,20 +159,36 @@
<div class="mt-4" style="zoom:80%"> <div class="mt-4" style="zoom:80%">
<button class="btn btn-success" onClick="showFilters()"><i class="fas fa-filter"></i></button> <button class="btn btn-success" onClick="showFilters()"><i class="fas fa-filter"></i></button>
<a class="btn btn-success" href="{{path('app_issuescrum',{id:scrum.id})}}"><i class="fas fa-ticket-alt"></i></a> <a class="btn btn-success" href="{{path('app_scrum_table',{id:scrum.id})}}"><i class="fas fa-ticket-alt"></i></a>
<a class="btn btn-success" href="{{path('app_scrum_stat',{id:scrum.id})}}"><i class="fas fa-chart-area"></i></a> <a class="btn btn-success" href="{{path('app_scrum_stat',{id:scrum.id})}}"><i class="fas fa-chart-area"></i></a>
<a href="{{giteaUrl}}/{{scrum.giteajson.owner.login}}/{{scrum.giteajson.name}}/issues/new" class="btn btn-success" target="_blank">Nouveau Ticket</a> <a href="{{giteaUrl}}/{{scrum.giteajson.owner.login}}/{{scrum.giteajson.name}}/issues/new" class="btn btn-success" target="_blank">Nouveau Ticket</a>
<span id="textfilters"></span> <span id="textfilters"></span>
</div> </div>
{% for column in scrum.scrumcolumns %} {% for column in tbissues %}
{% set idmiletone="-100" %} <div class="d-inline-block mt-3 align-top mb-5 p-2" data-column="{{column.gicol}}" style="width: 18rem;">
{% set tbidmiletone=[] %} <h2 style="text-transform: uppercase; text-align:center; font-size:26px">{{ column.nmcol }}</h2>
{% set haveissues=false %}
<div class="card d-inline-block mt-3 align-top mb-5" data-column="{{column.giteaid}}" style="width: 18rem;">
<div class="card-header">{{ column.name }}</div>
<div class="card-body p-1"> <div class="card-body p-1">
{% for issue in column.scrumissues %} {% for jalon in column.jalons %}
{% for sprint in jalon.sprints %}
<div class="card mb-3" data-column="{{column.gicol}}" data-milestone="{{jalon.gijal}}" data-sprint="{{sprint.idspr}}" data-millestonesprint="{{jalon.gijal~"-"~sprint.idspr}}">
<div class="card-header p-1 d-flex" style="font-size:16px; cursor:pointer;" onClick="$(this).next('.card-body').toggle()">
<div>
JALON = {{jalon.nmjal}}
{% if sprint.idspr!=-100 %}
<br>
SPRINT = {{sprint.nmspr}}
{% endif %}
</div>
<div class="ml-auto" data-weight="{{column.gicol~"-"~jalon.gijal~"-"~sprint.idspr}}">
</div>
</div>
<div class="card-body p-1">
<ul class="scrumcolumn list-group" style="min-height:50px" data-column="{{column.gicol}}" data-milestone="{{jalon.gijal}}" data-sprint="{{sprint.idspr}}" data-millestonesprint="{{jalon.gijal~"-"~sprint.idspr}}">
{% for issue in sprint.issues %}
{% set tosee=false %} {% set tosee=false %}
{% if app.session.get('viewclosed')=="true" and issue.giteastate=="closed" %} {% if app.session.get('viewclosed')=="true" and issue.giteastate=="closed" %}
{% set tosee=true %} {% set tosee=true %}
@ -153,29 +198,12 @@
{% endif %} {% endif %}
{% if tosee %} {% if tosee %}
{% set haveissues=true %}
{% if idmiletone!=issue.giteamilestone %}
{% if idmiletone!=-100 %}</ul>{% endif %}
{% if issue.giteamilestone is empty %}
{% set tbidmiletone = tbidmiletone|merge([-100]) %}
{% set idmilestone = -100 %}
{% set namemilestone = "Aucun" %}
{% else %}
{% set tbidmiletone = tbidmiletone|merge([issue.giteamilestone]) %}
{% set idmilestone = issue.giteamilestone %}
{% set namemilestone = issue.giteajson.milestone.title %}
{% endif %}
<h3 data-milestone="{{idmilestone}}">JALON = {{namemilestone}}</h3>
<ul class="scrumcolumn list-group" style="min-height:50px" data-column="{{column.giteaid}}" data-milestone="{{idmilestone}}">
{% set idmiletone=issue.giteamilestone %}
{% endif %}
{% set datalabels="" %} {% set datalabels="" %}
{% set datateams="" %} {% set datateams="" %}
{% set datatypes="" %} {% set datatypes="" %}
{% set dataprioritys="datapriority"~giteaprioritys|last %} {% set dataprioritys="datapriority"~giteaprioritys|last %}
{% set issuprioritycolor=prioritycolor %}
{% for label in issue.giteajson.labels %} {% for label in issue.giteajson.labels %}
{% if label.id not in giteacolumns and label.id in giteateams %} {% if label.id not in giteacolumns and label.id in giteateams %}
{% set datateams=datateams~"datateam"~label.id~" " %} {% set datateams=datateams~"datateam"~label.id~" " %}
@ -187,11 +215,10 @@
{% if label.id not in giteacolumns and label.id in giteaprioritys %} {% if label.id not in giteacolumns and label.id in giteaprioritys %}
{% set dataprioritys="datapriority"~label.id~" " %} {% set dataprioritys="datapriority"~label.id~" " %}
{% set issuprioritycolor="#"~label.color %}
{% endif %} {% endif %}
{% if label.id not in giteacolumns and label.id not in giteateams and label.id not in giteatypes and label.id not in giteaprioritys %}
{% set datalabels=datalabels~"datalabel"~label.id~" " %} {% set datalabels=datalabels~"datalabel"~label.id~" " %}
{% endif %}
{% endfor %} {% endfor %}
{% set dataassignees="" %} {% set dataassignees="" %}
@ -199,51 +226,154 @@
{% set dataassignees=dataassignees~"dataassignee"~assignee.id~" " %} {% set dataassignees=dataassignees~"dataassignee"~assignee.id~" " %}
{% endfor %} {% endfor %}
<div id="issu{{ issue.id }}" data-id="{{ issue.id }}" data-issue="{{ issue.id }}" data-column="{{column.giteaid}}" data-milestone="{{idmilestone}}" class="card mb-1 issue issue-{{issue.id}} {{datateams}} {{datatypes}} {{dataprioritys}} {{datalabels}} {{dataassignees}} state-{{issue.giteastate}}"> {% set backcolor="" %}
<div class="card-footer p-1" style="line-height:10px; border-top:none;"> {% if not issue.color is empty %}
<div class="float-left btn btn-link p-0 m-0 fas fa-arrows-alt" style="cursor:move"></div> {% set backcolor="background-color:"~issue.color~";" %}
<a target="_blank" class="modcolumn btn btn-link float-right fa fa-file p-0 m-0" href="{{issue.giteajson.html_url}}"></a> {% elseif issue.scrumissueblock and not issue.scrumissueblock.color is empty %}
{% set backcolor="background-color:"~issue.scrumissueblock.color~";" %}
{% endif %}
{% set notes=(not issue.notes is empty?issue.notes|striptags|replace({'\n': '<br>', '\r': '<br>'})|raw:"") %}
<div id="issu{{ issue.id }}" data-id="{{ issue.id }}" data-issue="{{ issue.id }}" data-column="{{column.gicol}}" data-milestone="{{jalon.gijal}}" data-sprint="{{sprint.idspr}}" class="card mb-1 issue issue-{{issue.id}} {{datateams}} {{datatypes}} {{dataprioritys}} {{datalabels}} {{dataassignees}} state-{{issue.giteastate}}" style="border-left: 10px solid {{issuprioritycolor}}" {{ (not notes is empty?'title='~notes:'') }}>
<div class="card-footer p-1 d-flex" style="line-height:16px; border-top:none; {{ backcolor }}" >
<div class="flex-grow-1 d-flex align-items-center" style="max-width:224px";>
<div class="pr-2 issu-id" style="cursor:move">
#{{issue.giteanumber}}
</div>
<div class="text-small" style="cursor:pointer; word-break: break-word;" onClick="$('#issu-detail{{ issue.id }}').toggle()">
{% if not issue.scrumissueblock is empty %}
<div class="text-verysmall" style="margin-bottom:-5px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis; width: 160px;">
#{{issue.scrumissueblock.giteanumber}} = {{issue.scrumissueblock.giteatitle}}
</div>
{% endif %}
<div style="line-height:13px;">{{ issue.giteatitle }}</div>
{% for depend in issue.scrumissuedependencies %}
{% if loop.first %}
<div class="text-verysmall" style="margin-top: 5px;line-height:11px; width:160px;">
{% endif %}
{% set style="" %}
{% if depend.giteastate=="closed" %}
{% set style="text-decoration: line-through;" %}
{% endif %}
<div style="white-space: nowrap;overflow: hidden;text-overflow: ellipsis;{{style}}">#{{depend.giteanumber}} = {{depend.giteatitle}}</div>
{% if loop.last %}
</div>
{% endif %}
{% endfor %}
</div>
</div> </div>
<div class="card-body p-1" style="line-height:10px;"> <div id="viewissu{{ issue.id }}" class="viewissu mb-2" onMouseenter="issuhover(this,{{ issue.id }})" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}" type="button" style="line-height:9px; text-align:center;">
<div> <i class="btn btn-link fas fa-eye p-0 m-0 fa-fw"></i>
<small> <br><span class="text-verysmall issue{{issue.id}}-weight">{{ issue.weight }}</span>
{{issue.giteanumber}} - {{ issue.giteatitle }}<br><br> </div>
<small>Crée le {{issue.giteajson.created_at|date("d/m/y")}}</small><small style ="float:right">Modifié le {{issue.giteajson.updated_at|date("d/m/y")}}</small> </div>
{% if issue.giteastate=="closed" %}
<br><small>Clos le {{issue.giteajson.closed_at|date("d/m/y")}}</small> <div id="submenu{{issue.id}}" class="submenu" onmouseleave="issuout(this)" style="
position: absolute;
left: 214px;
top: -1px;
width:300px;
z-index:1200;
display:none;
background-color:#f7f7f7;
flex-direction: column;
border: 1px solid #cdcdcd;
border-radius: .25rem;
">
<div id="viewissu{{ issue.id }}" class="viewissu mb-2" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}">
<i class="btn fas fa-eye p-0 m-0 fa-fw pl-1 pl-1"></i>
<span style="cursor:pointer;">
Aperçu Ticket
</span>
</div>
<a target="_blank" href="{{issue.giteajson.html_url}}" style="cursor:pointer" class="modcolumn mb-2">
<i class="btn fa fa-file fa-fw p-0 m-0 pl-1 pl-1"></i>
<span>Modifier dans Gitea</span>
</a>
<div id="modissu{{ issue.id }}" class="modissu mb-2" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}">
<i class="btn fas fa-weight-hanging p-0 m-0 fa-fw pl-1 pl-1"></i>
<span style="cursor:pointer;">
Modifier le Poids = <span class="issue{{issue.id}}-weight">{{ issue.weight }}</span>
</span>
</div>
{% if issue.scrumissueblock is empty %}
<div id="lockissu{{ issue.id }}" class="lockissu mb-2" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}">
<i class="btn fas fa-lock p-0 m-0 fa-fw pl-1 pl-1"></i>
<span style="cursor:pointer;">
Ajouter ce ticket comme bloquant à
</span>
</div>
{% else %}
<div id="unlockissu{{ issue.id }}" class="unlockissu mb-2" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}">
<i class="btn fas fa-lock p-0 m-0 fa-fw pl-1 pl-1"></i>
<span style="cursor:pointer;">
N'est plus bloquant pour #{{ issue.scrumissueblock.giteanumber}}
</span>
</div>
{% endif %} {% endif %}
<br><br>
<div id="assigneissu{{ issue.id }}" class="assigneissu mb-2" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}">
<i class="btn fas fa-users p-0 m-0 fa-fw pl-1 pl-1"></i>
<span style="cursor:pointer;">
Affecté à
</span>
</div>
<div id="notesissu{{ issue.id }}" class="notesissu mb-2" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}">
<i class="btn fas fa-clipboard p-0 m-0 fa-fw pl-1 pl-1"></i>
<span style="cursor:pointer;">
Notes
</span>
</div>
<div>
<input type="text" id="colorissu{{ issue.id }}" data-issue="{{ issue.id }}" class="pick-a-color form-control form-control spectrum sp-colorize" value="{{issue.color}}" autocomplete="off">
</div>
</div>
<div id="issu-detail{{ issue.id }}" class="card-body p-1 issu-detail">
<div>
{% for label in issue.giteajson.labels %} {% for label in issue.giteajson.labels %}
{% if label.id not in giteacolumns and label.id in giteaprioritys %} {% if label.id not in giteacolumns and label.id in giteaprioritys %}
<span class="btn-link tag" style="background-color:#{{label.color}}"> <span class="btn-link tag" style="background-color:#{{label.color}}">
<i class="fas fa-tag"></i> <i class="fas fa-clock"></i>
{{ label.name }} {{ label.name }}
</span> </span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<br> </div>
<div>
{% for label in issue.giteajson.labels %} {% for label in issue.giteajson.labels %}
{% if label.id not in giteacolumns and label.id in giteatypes %} {% if label.id not in giteacolumns and label.id in giteatypes %}
<span class="btn-link tag" style="background-color:#{{label.color}}"> <span class="btn-link tag" style="background-color:#{{label.color}}">
<i class="fas fa-tag"></i> <i class="fas fa-quote-right"></i>
{{ label.name }} {{ label.name }}
</span> </span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<br> </div>
<div>
{% for label in issue.giteajson.labels %} {% for label in issue.giteajson.labels %}
{% if label.id not in giteacolumns and label.id in giteateams %} {% if label.id not in giteacolumns and label.id in giteateams %}
<span class="btn-link tag" style="background-color:#{{label.color}}"> <span class="btn-link tag" style="background-color:#{{label.color}}">
<i class="fas fa-tag"></i> <i class="fas fa-users"></i>
{{ label.name }} {{ label.name }}
</span> </span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<br> </div>
<div>
{% for label in issue.giteajson.labels %} {% for label in issue.giteajson.labels %}
{% if label.id not in giteacolumns and label.id not in giteateams and label.id not in giteaprioritys and label.id not in giteatypes %} {% if label.id not in giteacolumns and label.id not in giteateams and label.id not in giteaprioritys and label.id not in giteatypes %}
<span class="btn-link tag" style="background-color:#{{label.color}}"> <span class="btn-link tag" style="background-color:#{{label.color}}">
@ -252,6 +382,7 @@
</span> </span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div>
<div> <div>
{% for assignee in issue.giteajson.assignees %} {% for assignee in issue.giteajson.assignees %}
@ -259,31 +390,32 @@
{% endfor %} {% endfor %}
</div> </div>
<div id="modissu{{ issue.id }}" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}" type="button" class="modissu btn btn-link float-right"> <div id="modissu{{ issue.id }}" class="modissu btn btn-link d-flex justify-content-end align-items-center" data-issue="{{ issue.id }}" data-giteaid="{{issue.giteanumber}}" data-giteatitle="{{ issue.giteatitle }}" type="button">
<i class="fas fa-weight-hanging"></i> = <span id="issue{{issue.id}}-weight">{{ issue.weight }}</span> <i class="fas fa-weight-hanging"></i> = <span class="issue{{issue.id}}-weight">{{ issue.weight }}</span>
</div> </div>
</small>
<div class="d-flex text-small">
<div class="text-small">Crée le {{issue.giteajson.created_at|date("d/m/y")}}</div>
<div class="text-small text-right ml-auto">Modifié le {{issue.giteajson.updated_at|date("d/m/y")}}</div>
</div>
<div class="text-small">
<div class="text-small">Par {{issue.giteajson.user.login}}</div>
{% if issue.giteastate=="closed" %}
<div class="text-small text-right ml-auto">
Clos le {{issue.giteajson.closed_at|date("d/m/y")}}
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if haveissues %}
</ul> </ul>
{% endif %} </div>
</div>
{% for giteamilestone in giteamilestones %} {% endfor %}
{% if giteamilestone.id not in tbidmiletone %} {% endfor %}
<h3 data-milestone="{{giteamilestone.id}}"> JALON = {{giteamilestone.title}}</h3>
<ul class="scrumcolumn list-group" style="min-height:50px" data-column="{{ column.giteaid }}" data-milestone="{{giteamilestone.id}}"></ul>
{% endif %}
{%endfor%}
{% if -100 not in tbidmiletone %}
<h3 data-milestone="-100"> JALON = Aucun</h3>
<ul class="scrumcolumn list-group" style="min-height:50px" data-column="{{ column.giteaid }}" data-milestone="-100"></ul>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@ -313,12 +445,89 @@
</div> </div>
</div> </div>
</div> </div>
<div id="mymodalblock" class="modal" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group ">
<label class="control-label required" for="issu_weight">
Le ticket en cours bloque le ticket suivant
</label>
<input type="hidden" id="modal-issueid" name="modal-issueid" required="required" class=" form-control" value="">
<input type="integer" id="modal-issueblocked" name="modal-issueweight" required="required" class=" form-control" value="">
</div>
<button id="issu_blockupdate" class="btn btn-success">Enregistrer</button>
</div>
</div>
</div>
</div>
<div id="mymodalassignees" class="modal" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input type="hidden" id="modal-issueid" name="modal-issueid" required="required" class=" form-control" value="">
<div style="width:100%">
<label class="control-label required" for="issu_weight">
Affecté à
</label>
<select id="modal-assignees" multiple="multiple" class="form-control mb-3">
{% for giteaassignee in giteaassignees %}
<option value="{{giteaassignee.login}}">{{giteaassignee.login}}</option>
{% endfor %}
</select>
</div>
<button id="issu_assigne" class="btn btn-success mt-3">Enregistrer</button>
</div>
</div>
</div>
</div>
<div id="mymodalnotes" class="modal" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input type="hidden" id="modal-issueid" name="modal-issueid" required="required" class=" form-control" value="">
<div style="width:100%">
<textarea id="modal-notes" style="width:100%; height:300px;">
</textarea>
</div>
<button id="issu_notes" class="btn btn-success mt-3">Enregistrer</button>
</div>
</div>
</div>
</div>
{% set end = microtime(true) %}
{% set duration = end - start %}
<p>render time: {{ duration }} seconds</p>
{% endblock %} {% endblock %}
{% block localjavascript %} {% block localjavascript %}
function showFilters() { function showFilters() {
if($("#filters").hasClass("d-flex")) { if($("#filters").hasClass("d-flex")) {
toshow=0; toshow=0;
@ -343,16 +552,43 @@
} }
$(document).ready(function() { $(document).ready(function() {
$(".issue").tooltip({
content: function() {
return $(this).data('tooltip'); // Utilise le contenu de l'attribut data-tooltip
},
items: '[data-tooltip]', // Spécifie les éléments qui déclenchent le tooltip
html: true // Autorise le HTML dans le tooltip (si nécessaire pour certains tooltips personnalisés)
});
// Apply Filter // Apply Filter
function showhide() { function showhide() {
if($("#filtermilestones").val().length !== 0) { // Vue condensée
if($("#viewcondensed").is(':checked')) {
$(".issu-detail").hide();
}
else {
$(".issu-detail").show();
}
// Afficher masquer les jalons
if($("#filtermilestones").val().length !== 0 && $("#filtersprints").val().length === 0) {
$("[data-milestone]").hide(); $("[data-milestone]").hide();
$.each($("#filtermilestones").val(), function( index, value ) { $.each($("#filtermilestones").val(), function( index, value ) {
console.log(value);
$("[data-milestone="+value+"]").show(); $("[data-milestone="+value+"]").show();
}); });
} }
else $("[data-milestone]").show(); else $("[data-milestone]").show();
// Afficher masquer les sprints
if($("#filtersprints").val().length !== 0) {
$("[data-millestonesprint]").hide();
$.each($("#filtersprints").val(), function( index, value ) {
$("[data-millestonesprint="+value+"]").show();
});
}
else if($("#filtermilestones").val().length === 0)$("[data-millestonesprint]").show();
$(".issue").show(); $(".issue").show();
$( ".issue" ).each(function( index ) { $( ".issue" ).each(function( index ) {
@ -419,6 +655,15 @@
}); });
} }
if($("#filtersprints").val().length!==0) {
data = $("#filtersprints").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>SPRINT</b> =";
$.each($("#filtersprints").val(), function( index, value ) {
if(index>0)textfilters=textfilters+" &";
textfilters=textfilters+" "+data[index].text;
});
}
if($("#filterteams").val().length!==0) { if($("#filterteams").val().length!==0) {
data = $("#filterteams").select2('data'); data = $("#filterteams").select2('data');
textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>EQUIPES</b> ="; textfilters=textfilters+"&nbsp;&nbsp;&nbsp;&nbsp;<b>EQUIPES</b> =";
@ -473,23 +718,61 @@
}); });
} }
$("#textfilters").html(textfilters); $("#textfilters").html(textfilters);
} }
function refreshinfo() {
$(document).on('click','.viewissu',function(){
$(".submenu").hide();
url="{{path('app_scrumissue_view',{id:"xxx"})}}";
url=url.replace("xxx",$(this).data("issue"));
ModalLoad('mymodallarge','Aperçu Ticket',url);
});
$(document).on('click','.lockissu',function(){
$(".modal-title").html("#"+$(this).data("giteaid")+" - "+$(this).data("giteatitle"));
$("#modal-issueid").val($(this).data("issue"));
$("#modal-issueblocked").val("");
$("#mymodalblock").modal('show');
$("#modal-issueblocked").focus();
});
$("#issu_blockupdate").click(function(){
$.ajax({ $.ajax({
method: "POST", method: "POST",
url: "{{path("app_scrum_info",{id:scrum.id})}}", url: "{{path("app_scrumissue_block")}}",
data: {
id:$("#modal-issueid").val(),
issueblocked:$("#modal-issueblocked").val(),
},
success: function(data) { success: function(data) {
$(".totalweight").html("0"); location.reload();
},
error: function (request, status, error) {
$("#issueblocked").modal('hide');
}
});
});
Object.entries(data.weights).forEach(entry => { $(document).on('click','.unlockissu',function(){
const [key, value] = entry; if (window.confirm("Souhaitez-vous enlever le blocage ?")) {
$("#total"+key).html(value); $.ajax({
}); method: "POST",
url: "{{path("app_scrumissue_unblock")}}",
data: {
id:$(this).data("issue"),
},
success: function(data) {
location.reload();
},
error: function (request, status, error) {
alert("pb sur le déblocage");
} }
}); });
} }
});
$(document).on('click','.modissu',function(){ $(document).on('click','.modissu',function(){
$(".modal-title").html("#"+$(this).data("giteaid")+" - "+$(this).data("giteatitle")); $(".modal-title").html("#"+$(this).data("giteaid")+" - "+$(this).data("giteatitle"));
@ -518,7 +801,7 @@
weight:$("#modal-issueweight").val(), weight:$("#modal-issueweight").val(),
}, },
success: function(data) { success: function(data) {
$("#issue"+$("#modal-issueid").val()+"-weight").html($("#modal-issueweight").val()); $(".issue"+$("#modal-issueid").val()+"-weight").html($("#modal-issueweight").val());
refreshinfo(); refreshinfo();
$("#mymodalissue").modal('hide'); $("#mymodalissue").modal('hide');
}, },
@ -528,6 +811,108 @@
}); });
}); });
$(".pick-a-color").on("change", function() {
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_color")}}",
data: {
id:$(this).data("issue"),
color:$(this).val(),
},
success: function(data) {
location.reload();
},
error: function (request, status, error) {
alert("pb sur le set color");
}
});
});
$(document).on('click','.assigneissu',function(){
$(".modal-title").html("#"+$(this).data("giteaid")+" - "+$(this).data("giteatitle"));
$("#modal-issueid").val($(this).data("issue"));
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_info")}}",
data: {
id:$("#modal-issueid").val(),
},
success: function(data) {
var tbassignees=[];
if(data.giteajson.assignees) {
for (let assignee of data.giteajson.assignees) {
tbassignees.push(assignee.login);
}
}
$('#modal-assignees').select2();
$('#modal-assignees').val(tbassignees);
$('#modal-assignees').trigger('change');
$("#mymodalassignees").modal('show');
$(".submenu").hide();
$("#modal-assignees").focus();
},
});
});
$("#issu_assigne").click(function(){
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_assigne")}}",
data: {
id:$("#modal-issueid").val(),
assignees: $("#modal-assignees").val(),
},
success: function(data) {
location.reload();
},
error: function (request, status, error) {
alert("pb sur l'affectation au ticket");
}
});
});
$(document).on('click','.notesissu',function(){
$(".modal-title").html("#"+$(this).data("giteaid")+" - "+$(this).data("giteatitle"));
$("#modal-issueid").val($(this).data("issue"));
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_info")}}",
data: {
id:$("#modal-issueid").val(),
},
success: function(data) {
$('#modal-notes').val(data.notes);
$("#mymodalnotes").modal('show');
$(".submenu").hide();
$("#modal-notes").focus();
},
});
});
$("#issu_notes").click(function(){
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_notes")}}",
data: {
id:$("#modal-issueid").val(),
notes: $("#modal-notes").val(),
},
success: function(data) {
location.reload();
},
error: function (request, status, error) {
alert("pb sur enregistrement du ticket");
}
});
});
// Filter Milestones // Filter Milestones
function filtermilestones() { function filtermilestones() {
$.ajax({ $.ajax({
@ -557,6 +942,35 @@
filtermilestones(); filtermilestones();
}); });
// Filter Sprint
function filtersprints() {
$.ajax({
method: "POST",
url: "{{ path('app_user_preference') }}",
data: {
key:'filtersprints',
id:{{scrum.id}},
value: $("#filtersprints").val()
}
});
showhide();
}
$('#filtersprints').select2({sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),});
{% if filtersprints %}
{% for sprint in filtersprints %}
$("#filtersprints").val($("#filtersprints").val().concat("{{sprint}}"));
{%endfor%}
$('#filtersprints').trigger('change');
{% endif %}
$('#filtersprints').on("select2:select", function(e) {
filtersprints();
});
$('#filtersprints').on("select2:unselect", function(e) {
filtersprints();
});
// Filter Teams // Filter Teams
function filterteams() { function filterteams() {
$.ajax({ $.ajax({
@ -728,6 +1142,26 @@
filterexcludes(); filterexcludes();
}); });
// View condensed
{% if viewcondensed %}
$("#viewcondensed").prop( "checked", {{ viewcondensed }} )
{% endif %}
$('#viewcondensed').change(function() {
$.ajax({
method: "POST",
url: "{{ path('app_user_preference') }}",
data: {
key:'viewcondensed',
id:{{scrum.id}},
value: this.checked
}
}).done(function( data ) {
showhide();
});
});
// Resume filtre // Resume filtre
{% if not showfilters %} {% if not showfilters %}
$("#filters").addClass("d-none"); $("#filters").addClass("d-none");
@ -739,23 +1173,28 @@
refreshinfo(); refreshinfo();
$("#mycontent").show(); $("#mycontent").show();
lastupdate="{{scrum.updatedate|date("Ymd H:i:s")}}"; lastupdate="{{updatedate|date("Ymd H:i")}}";
console.log(lastupdate);
// Sort columns
$( ".scrumcolumn" ).sortable({ $( ".scrumcolumn" ).sortable({
handle: ".fa-arrows-alt", handle: ".issu-id",
connectWith: ".scrumcolumn", connectWith: ".scrumcolumn",
cursor: "move", cursor: "move",
update: function( event, ui ) { start: function( event, ui ) {
$(".submenu").hide();
},
stop: function( event, ui ) {
id=$(ui.item).data("issue"); id=$(ui.item).data("issue");
oldcolumn=$(ui.item).data("column"); oldcolumn=$(ui.item).data("column");
oldmilestone=$(ui.item).data("milestone"); oldmilestone=$(ui.item).data("milestone");
oldsprint=$(ui.item).data("sprint");
newcolumn=$(ui.item).parent().data("column"); newcolumn=$(ui.item).parent().data("column");
newmilestone=$(ui.item).parent().data("milestone"); newmilestone=$(ui.item).parent().data("milestone");
console.log("ID = "+id+" = Column : "+oldcolumn+">>"+newcolumn+" = Milestone : "+oldmilestone+">>"+newmilestone ); newsprint=$(ui.item).parent().data("sprint");
console.log("ID = "+id+" = Column : "+oldcolumn+">>"+newcolumn+" = Milestone : "+oldmilestone+">>"+newmilestone+" = Sprint : "+oldsprint+">>"+newsprint );
if(oldcolumn!=newcolumn||oldmilestone!=newmilestone) {
if(oldcolumn!=newcolumn||oldmilestone!=newmilestone||oldsprint!=newsprint) {
$.ajax({ $.ajax({
method: "POST", method: "POST",
url: "{{path("app_scrumissue_change")}}", url: "{{path("app_scrumissue_change")}}",
@ -763,13 +1202,17 @@
id:id, id:id,
oldcolumn:oldcolumn, oldcolumn:oldcolumn,
oldmilestone:oldmilestone, oldmilestone:oldmilestone,
oldsprint:oldsprint,
newcolumn:newcolumn, newcolumn:newcolumn,
newmilestone:newmilestone, newmilestone:newmilestone,
newsprint:newsprint
}, },
success: function(data) { success: function(data) {
$(ui.item).data("column",newcolumn); $(ui.item).data("column",newcolumn);
$(ui.item).data("milestone",newmilestone); $(ui.item).data("milestone",newmilestone);
$(ui.item).data("sprint",newsprint);
if(data) lastupdate=data; if(data) lastupdate=data;
console.log(lastupdate);
refreshinfo(); refreshinfo();
}, },
@ -798,9 +1241,7 @@
}, },
}); });
var intervalId = window.setInterval(function(){ var intervalId = window.setInterval(function(){
console.log(lastupdate);
$.ajax({ $.ajax({
method: "POST", method: "POST",
url: "{{path("app_scrumissue_ctrlchange")}}", url: "{{path("app_scrumissue_ctrlchange")}}",
@ -809,12 +1250,65 @@
lastupdate:lastupdate lastupdate:lastupdate
}, },
success: function(fgupdated) { success: function(fgupdated) {
if(fgupdated) { if(fgupdated=="1") {
$("#haveupdate").show(); $("#haveupdate").show();
} }
} }
}); });
}, 60000); }, 300000);
}); });
function refreshinfo() {
$.ajax({
method: "POST",
url: "{{path("app_scrum_info",{id:scrum.id})}}",
success: function(data) {
html="";
$("[data-weight]").html("");
Object.entries(data.tbestim).forEach(entry => {
const [keyj, jalon] = entry;
html+='<table style="color:var(--colorftbodydark);zoom:75%; width:100%; border:1px solid var(--colorbgbodylight)">';
html+='<tr>';
html+='<td class="pl-1">'+jalon.nmjal+'</td>';
html+='<td class="pr-1" style="width:30px;text-align:right;">'+jalon.nbjrs+'</span></td>';
html+='</tr>';
nofirst=false;
Object.entries(jalon.sprints).forEach(entry => {
const [keys, sprint] = entry;
if(nofirst || sprint.idspr!=-100) {
html+='<tr>';
html+='<td class="pl-1">'+sprint.nmspr+'</td>';
html+='<td class="pr-1" style="width:30px;text-align:right;">'+sprint.nbjrs+'</span></td>';
html+='</tr>';
}
Object.entries(sprint.columns).forEach(entry => {
const [keys, column] = entry;
$("[data-weight="+column.gicol+"-"+jalon.gijal+"-"+sprint.idspr+"]").html(column.nbjrs);
});
nofirst=true;
});
html+='</table>';
});
$("#tbestim").html(html);
}
});
}
function issuhover(btn,idissu) {
$(".submenu").hide();
$("#submenu"+idissu).css("display","flex");
}
function issuout(menu) {
$(menu).css("display","none");
}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block body %}
{% endblock %}
{% block localjavascript %}
$(document).ready(function() {
window.parent.$("#mymodalsprint").modal('hide');
});
{% endblock %}

View File

@ -0,0 +1,52 @@
{% extends 'base.html.twig' %}
{% block body %}
{{ form_start(form) }}
{{ form_widget(form.submit) }}
<a class="btn btn-secondary" onClick="closeModal();">Annuler</a>
{% if mode=="update" %}
<a href="{{ path('app_scrumsprint_delete',{'id':scrumsprint.id}) }}"
class="btn btn-danger float-right"
data-method="delete"
data-confirm="Êtes-vous sûr de vouloir supprimer cet entregistrement ?">
Supprimer
</a>
{% endif %}
<br><br>
{% if app.session.flashbag.has('error') %}
<div class='alert alert-danger' style='margin: 5px 0px'>
<strong>Erreur</strong><br>
{% for flashMessage in app.session.flashbag.get('error') %}
{{ flashMessage }}<br>
{% endfor %}
</div>
{% endif %}
{% if app.session.flashbag.has('notice') %}
<div class='alert alert-info' style='margin: 5px 0px'>
<strong>Information</strong><br>
{% for flashMessage in app.session.flashbag.get('notice') %}
{{ flashMessage }}<br>
{% endfor %}
</div>
{% endif %}
{{ form_row(form.name) }}
{{ form_row(form.giteamilestone) }}
{{ form_row(form.closed) }}
{{ form_end(form) }}
{% endblock %}
{% block localjavascript %}
$(document).ready(function() {
$("#scrumsprint_name").focus();
});
function closeModal() {
window.parent.$("#mymodalsprint").modal('hide');
}
{% endblock %}

View File

@ -1,10 +1,15 @@
{% extends 'base.html.twig' %}
{% block body %}
{{wssurl}}
<div id="chat" class="text-center mt-5"> <div id="chat" class="text-center mt-5">
<div class="mb-2">online</div> <div class="mb-2">online</div>
<div id="online" style="mt-2"></div> <div id="online" style="mt-2"></div>
</div> </div>
{% endblock %}
{% block localjavascript %}
{% if wssuse %} {% if wssuse %}
<script>
var conn; var conn;
function connect() { function connect() {
@ -20,13 +25,14 @@
subscribe("home",1,"{{userkey}}"); subscribe("home",1,"{{userkey}}");
sendMessage({command: "alive"}); sendMessage({command: "alive"});
}; };
conn.onmessage = function(e) { conn.onmessage = function(e) {
ret=JSON.parse(e.data); ret=JSON.parse(e.data);
console.log(ret.log);
switch(ret.command) { switch(ret.command) {
case "alive" : case "alive" :
if(!$('#online'+ret.from.id).length) { if(!$('#online'+ret.from.id).length) {
console.log(ret);
html='<img id="online'+ret.from.id+'" src="'+ret.from.avatar+'" class="avatar ml-2 mr-2" title="'+ret.from.displayname+'">'; html='<img id="online'+ret.from.id+'" src="'+ret.from.avatar+'" class="avatar ml-2 mr-2" title="'+ret.from.displayname+'">';
$("#online").append(html); $("#online").append(html);
} }
@ -57,5 +63,5 @@
} }
connect(); connect();
</script>
{% endif %} {% endif %}
{% endblock %}

View File

@ -66,8 +66,6 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="nav-item"><a href="{{path("app_scrum")}}">Scrums</a></li>
<a href="{{path("app_issue")}}">Issues</a>
</ul> </ul>
</div> </div>
@ -154,6 +152,10 @@
{% endblock %} {% endblock %}
<script>
{{ include('Include/javascript.js.twig') }}
</script>
<script> <script>
{% if app.session.get('viewclosed') %} {% if app.session.get('viewclosed') %}