block / unblock && sprint closed

This commit is contained in:
afornerot 2024-04-12 16:30:36 +02:00
parent 0acb4e1777
commit 9dc93daab8
15 changed files with 343 additions and 26 deletions

View File

@ -43,7 +43,7 @@
"symfony/web-link": "5.1.*",
"symfony/webpack-encore-bundle": "^1.7",
"symfony/yaml": "5.1.*",
"tetranz/select2entity-bundle": "^3.0",
"tetranz/select2entity-bundle": "^3.0"
},
"require-dev": {
"symfony/debug-pack": "*",

View File

@ -338,6 +338,14 @@ app_scrumissue_update:
path: /user/scrumissue/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_ctrlchange:
path: /user/scrumissue/ctrlchange
defaults: { _controller: App\Controller\ScrumissueController:ctrlchange }

View File

@ -199,7 +199,7 @@ class ScrumController extends AbstractController
$viewclosed = $this->get('session')->get("viewclosed");
foreach($issues as $issue) {
// bypass closed
if($viewclosed=="false"&&$issue->getGiteastate()=="closed") continue;
if($viewclosed=="false"&&($issue->getGiteastate()=="closed"||($issue->getScrumsprint()&&$issue->getScrumsprint()->getClosed()))) continue;
// Ids
$idcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getId():$firstcolumn->getId());
@ -334,8 +334,11 @@ class ScrumController extends AbstractController
// 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(),
@ -398,7 +401,7 @@ class ScrumController extends AbstractController
'giteaassignees' => $giteaassignees,
'giteacolumns' => $giteacolumns,
'giteamilestones' => $giteamilestones,
'sprints' => $data->getScrumsprints(),
'sprints' => $data->getScrumsprintstosee($viewclosed),
'giteateams' => $giteateams,
'giteaprioritys' => $giteaprioritys,
'giteatypes' => $giteatypes,
@ -662,7 +665,7 @@ class ScrumController extends AbstractController
$viewclosed = $this->get('session')->get("viewclosed");
foreach($issues as $issue) {
// bypass closed
if($viewclosed=="false"&&$issue->getGiteastate()=="closed") continue;
if($viewclosed=="false"&&($issue->getGiteastate()=="closed"||($issue->getScrumsprint()&&$issue->getScrumsprint()->getClosed()))) continue;
// Ids
$idcol=($issue->getScrumcolumn()?$issue->getScrumcolumn()->getId():$firstcolumn->getId());

View File

@ -237,6 +237,44 @@ class ScrumissueController extends AbstractController
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);
dump($response);
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 ctrlchange(Request $request)
{
$em = $this->getDoctrine()->getManager();

View File

@ -145,7 +145,9 @@ class ScrumsprintController extends AbstractController
$scrumsprints = $scrum->getScrumsprints();
$output=array();
foreach($scrumsprints as $scrumsprint) {
array_push($output,array("id"=>$scrumsprint->getId(),"name"=>"<b>".$scrumsprint->getName()."</b><br><small>liè au jalon gitea ".$scrumsprint->getGiteamilestonename()."</small>"));
$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));

View File

@ -114,6 +114,18 @@ class Scrum
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()
{
$this->users = new ArrayCollection();

View File

@ -71,22 +71,33 @@ class Scrumissue
private $giteajson;
/**
* @ORM\ManyToOne(targetEntity="Scrum", inversedBy="Scrumissues")
* @ORM\ManyToOne(targetEntity="Scrum", inversedBy="scrumissues")
*/
private $scrum;
/**
* @ORM\ManyToOne(targetEntity="Scrumcolumn", inversedBy="Scrumissues")
* @ORM\ManyToOne(targetEntity="Scrumcolumn", inversedBy="scrumissues")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $scrumcolumn;
/**
* @ORM\ManyToOne(targetEntity="Scrumsprint", inversedBy="Scrumissues")
* @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)
*/
private $scrumissuedependencies;
/**
* @ORM\OneToMany(targetEntity="Userpoker", mappedBy="scrumissue", cascade={"persist"}, orphanRemoval=true)
*/
@ -95,6 +106,8 @@ class Scrumissue
public function __construct()
{
$this->userpokers = new ArrayCollection();
$this->scrumissuedependcies = new ArrayCollection();
$this->scrumissuedependencies = new ArrayCollection();
}
public function getId(): ?int
@ -277,6 +290,46 @@ class Scrumissue
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;
}
}

View File

@ -33,6 +33,11 @@ class Scrumsprint
*/
private $rowid;
/**
* @ORM\Column(type="boolean")
*/
private $closed;
/**
* @ORM\Column(type="integer", nullable=true)
*/
@ -155,5 +160,17 @@ class Scrumsprint
return $this;
}
public function getClosed(): ?bool
{
return $this->closed;
}
public function setClosed(bool $closed): self
{
$this->closed = $closed;
return $this;
}
}

View File

@ -32,6 +32,11 @@ class ScrumsprintType extends AbstractType
]
);
$choices=["non"=>0,"oui"=>1];
$builder->add('closed',
ChoiceType::class, ['choices' => $choices]
);
$choices=[];
foreach($options["giteamilestones"] as $milestone) {
$choices[$milestone->title]=$milestone->id;

View File

@ -36,6 +36,9 @@ class ScrumRepository extends ServiceEntityRepository
return $scrums;
}
}
public function getGitea($scrum,&$giteaassignees,&$giteacolumns,&$giteamilestones,&$giteateams,&$giteaprioritys,&$giteatypes,&$gitealabels, $forcereload=false) {
$viewclosed = $this->session->get("viewclosed");
@ -272,6 +275,19 @@ class ScrumRepository extends ServiceEntityRepository
}
}
$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->flush();

View File

@ -317,7 +317,6 @@ class giteaService
else return $response->body;
}
public function getissuetimelines($owner,$name,$index) {
$apiurl = $this->url."/repos/$owner/$name/issues/$index/timeline";
$response=$this->api("GET",$apiurl,null,$this->session->get("giteatoken"));
@ -325,6 +324,33 @@ class giteaService
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") 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) {
// Entete
$headers = [

View File

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

View File

@ -63,6 +63,11 @@
<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">
@ -423,6 +428,15 @@
loadscrumsprints();
});
$('#viewsprintclosed').change(function() {
if(this.checked) {
$(".sprintclosed").removeClass("hidden");
}
else {
$(".sprintclosed").addClass("hidden");
}
});
function loadscrumsprints() {
$("#scrumsprints").empty();
$.ajax({
@ -431,7 +445,17 @@
success: function(datas, dataType)
{
jQuery.each(datas, function(i, wid) {
html ='<li data-id="'+wid.id+'" class="list-group-item d-flex justify-content-between">';
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">';

View File

@ -221,23 +221,45 @@
<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}}">
<div class="card-footer p-1 d-flex" style="line-height:16px; border-top:none;">
<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()">{{ issue.giteatitle }}</div>
<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: 90%;">
#{{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">
{% 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 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;">
<i class="btn btn-link fas fa-eye p-0 m-0 fa-fw"></i>
<br><span class="text-verysmall issue{{issue.id}}-weight">{{ issue.weight }}</span>
</div>
</div>
<div id="submenu{{issue.id}}" class="submenu" onmouseleave="issuout(this)" style="
position: absolute;
left: 223px;
top: -1px;
width:200px;
width:300px;
z-index:1200;
display:none;
background-color:#f7f7f7;
@ -264,6 +286,23 @@
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>
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>
N'est plus bloquant pour #{{ issue.scrumissueblock.giteanumber}}
</span>
</div>
{% endif %}
</div>
<div id="issu-detail{{ issue.id }}" class="card-body p-1 issu-detail">
@ -368,6 +407,30 @@
</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>
{% endblock %}
{% block localjavascript %}
@ -557,10 +620,64 @@
$("#textfilters").html(textfilters);
}
$(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({
method: "POST",
url: "{{path("app_scrumissue_block")}}",
data: {
id:$("#modal-issueid").val(),
issueblocked:$("#modal-issueblocked").val(),
},
success: function(data) {
location.reload();
},
error: function (request, status, error) {
$("#issueblocked").modal('hide');
}
});
});
$(document).on('click','.unlockissu',function(){
if (window.confirm("Souhaitez-vous enlever le blocage ?")) {
$.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(){
$(".modal-title").html("#"+$(this).data("giteaid")+" - "+$(this).data("giteatitle"));
$("#modal-issueid").val($(this).data("issue"));
$.ajax({
method: "POST",
url: "{{path("app_scrumissue_info")}}",
@ -575,14 +692,6 @@
});
});
$(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);
});
$("#issu_update").click(function(){
$.ajax({
method: "POST",

View File

@ -3,7 +3,7 @@
{% block body %}
{{ form_start(form) }}
{{ form_widget(form.submit) }}
<button class="btn btn-secondary" onClick="closeModal();">Annuler</button>
<a class="btn btn-secondary" onClick="closeModal();">Annuler</a>
{% if mode=="update" %}
<a href="{{ path('app_scrumsprint_delete',{'id':scrumsprint.id}) }}"
@ -36,6 +36,7 @@
{{ form_row(form.name) }}
{{ form_row(form.giteamilestone) }}
{{ form_row(form.closed) }}
{{ form_end(form) }}
{% endblock %}
@ -46,6 +47,6 @@
});
function closeModal() {
window.parent.$("#mymodaltype").modal('hide');
window.parent.$("#mymodalsprint").modal('hide');
}
{% endblock %}