This commit is contained in:
2025-07-10 22:42:04 +02:00
parent 1cc4db4943
commit ee8220f059
4 changed files with 69 additions and 58 deletions

View File

@ -69,13 +69,21 @@ class IssueController extends AbstractController
return new JsonResponse(['message' => 'Erreur Redmine : Déplacement Interdit'], 400); return new JsonResponse(['message' => 'Erreur Redmine : Déplacement Interdit'], 400);
} }
try {
$payload = [ $payload = [
'fixed_version_id' => $targetVersion, 'fixed_version_id' => $targetVersion,
'agile_data_attributes' => ['agile_sprint_id' => $targetSprint], 'agile_data_attributes' => ['agile_sprint_id' => $targetSprint],
'status_id' => $targetStatus, 'status_id' => $targetStatus,
]; ];
$this->redmineService->updateIssue($id, $payload, $this->getParameter('redmineApikey')); $this->redmineService->updateIssue($id, $payload, $this->getUser()->getApikey());
} catch (\RuntimeException $e) {
// Récupère le message de l'exception
$errorMessage = $e->getMessage();
dump($e->getMessage());
// Par exemple, retour JSON d'erreur :
return new JsonResponse(['message' => $errorMessage], 400);
}
/* /*
$payload = $payload =
[ [

View File

@ -299,7 +299,7 @@ class RedmineService
public function getIssue(int $issueId, string $apiKey): array public function getIssue(int $issueId, string $apiKey): array
{ {
try { try {
$url = "{$this->baseUrl}/issues/{$issueId}.json?include=allowed_statuses"; $url = "{$this->baseUrl}/issues/{$issueId}.json?include=allowed_statuses,journals";
$response = $this->client->request('GET', $url, [ $response = $this->client->request('GET', $url, [
'headers' => [ 'headers' => [
@ -345,7 +345,7 @@ class RedmineService
} }
} }
public function updateIssue(int $id, array $data, string $apiKey): array public function updateIssue(int $id, array $data, ?string $apiKey): array
{ {
$url = $this->baseUrl.'/issues/'.$id.'.json'; $url = $this->baseUrl.'/issues/'.$id.'.json';
@ -358,25 +358,15 @@ class RedmineService
'json' => ['issue' => $data], 'json' => ['issue' => $data],
]); ]);
$statusCode = $response->getStatusCode(); if (200 !== $response->getStatusCode()) {
$content = trim($response->getContent(false)); if (401 === $response->getStatusCode()) {
throw new \RuntimeException('Erreur Redmine ('.$response->getStatusCode().') : Opération non autorisée, avez-vous placé votre apikey redmine sur votre profil');
// Si vide et code 200, cest peut-être une réussite silencieuse
if ('' === $content) {
return ['success' => true, 'message' => 'OK, mais pas de contenu'];
} }
$decoded = json_decode($content, true); throw new \RuntimeException('Erreur de communication avec Redmine : '.$response->getStatusCode());
if (isset($decoded['errors']) && is_array($decoded['errors']) && count($decoded['errors']) > 0) {
throw new \RuntimeException('Erreur Redmine : '.implode(', ', $decoded['errors']));
} }
return $decoded; return $response->toArray();
} catch (ClientExceptionInterface|ServerExceptionInterface $e) {
// Erreur HTTP (4xx ou 5xx)
$errorBody = $e->getResponse()->getContent(false);
throw new \RuntimeException('Erreur Redmine: '.$errorBody, $e->getCode(), $e);
} catch (TransportExceptionInterface $e) { } catch (TransportExceptionInterface $e) {
throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage()); throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage());
} }

View File

@ -6,7 +6,15 @@
<div class="btn btn-secondary" onClick="hideIssue()"><i class="fas fa-window-close"></i></div> <div class="btn btn-secondary" onClick="hideIssue()"><i class="fas fa-window-close"></i></div>
</div> </div>
<small class="text-muted">Projet : {{ issue.redmine.project.name }} • Tracker : {{ issue.redmine.tracker.name }}</small>
<div class="d-flex">
<small class="text-muted" style="flex-grow:1">Projet : {{ issue.redmine.project.name }} • Tracker : {{ issue.redmine.tracker.name }}</small>
{% for field in issue.redmine.custom_fields %}
{% if field.id==11 and field.value!="" %}
<small class="text-muted"><strong>{{ field.name }} =</strong> <a href="{{field.value}}" target="_blank">{{field.value|split('/')|last}}</a></small>
{% endif %}
{% endfor %}
</div>
</div> </div>
<div class="issueDescription card-body" style="height:500px;overflow-y:auto"> <div class="issueDescription card-body" style="height:500px;overflow-y:auto">
@ -44,36 +52,39 @@
</div> </div>
</div> </div>
<div>
{% for field in issue.redmine.custom_fields %}
{% if field.id==32 %}
<hr>
<strong>{{ field.name }} </strong><br>
{{ field.value|textile_to_html|raw }}<br>
{% endif %}
{% endfor %}
</div>
{% if issue.redmine.description %} {% if issue.redmine.description %}
<div class="mb-3"> <div class="mb-3">
<hr> <hr>
<strong>Description :</strong> <strong>Description :</strong>
<p>{{ issue.redmine.description|textile_to_html|raw }}</p> <p>{{ issue.redmine.description|textile_to_html|raw }}</p>
<hr>
</div> </div>
{% endif %} {% endif %}
{% if issue.redmine.custom_fields|length %} {% for journal in issue.redmine.journals %}
<div class="mb-3"> {% if journal.notes != "" %}
<hr> <div class="card">
<strong>Champs personnalisés :</strong> <div class="card-header">
<ul class="list-group"> <strong>Auteur =</strong> {{journal.user.name}}</strong><br>
{% for field in issue.redmine.custom_fields %} <small class="text-muted">Créé le =</strong>{{ journal.created_on|date('d/m/Y H:i') }}</small>
<li class="list-group-item d-flex justify-content-between align-items-center"> </div>
{{ field.name }} <div class="card-body">
<span class="text-muted"> {{ journal.notes |textile_to_html|raw }}
{% if field.multiple is defined and field.multiple and field.value is iterable %} </div>
{{ field.value|join(', ') }}
{% elseif field.value %}
{{ field.value }}
{% else %}
<em>—</em>
{% endif %}
</span>
</li>
{% endfor %}
</ul>
</div> </div>
{% endif %} {% endif %}
{% endfor %}
<hr>
{{dump(issue)}} {{dump(issue)}}
<br><br> <br><br>

View File

@ -192,8 +192,9 @@
<div class='filtreContainer'> <div class='filtreContainer'>
<label>Issue</label> <label>Issue</label>
<input type="number" id="issueSearchInput" class="form-control" placeholder="Rechercher une issue" /> <input type="number" id="issueSearchInput" class="form-control" placeholder="Rechercher une issue" />
<a href="{{redmineUrl}}/projects/{{project.id}}/issues/new" target="_blank" class="btn btn-primary btn-sm"><i class="fas fa-file"></i> Nouvelle Demande</a>
<label>Statut</label> <label style="margin-top:30px">Statut</label>
<select id="statusFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true"> <select id="statusFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
{% for statut in project.redmine.issue_statuses %} {% for statut in project.redmine.issue_statuses %}
{% if statut.id not in project.hiddenstatuses %} {% if statut.id not in project.hiddenstatuses %}
@ -381,9 +382,10 @@
}); });
// Scroll horizontal (retourne une promesse) // Scroll horizontal (retourne une promesse)
let paddingLeft = parseInt($('.scrumContainer').css('padding-left'), 315);
const scrollLeftPromise = new Promise(resolve => { const scrollLeftPromise = new Promise(resolve => {
$('html, body').animate({ $('html, body').animate({
scrollLeft: $element.offset().left - 315 scrollLeft: $element.offset().left - paddingLeft
}, 500, resolve); }, 500, resolve);
}); });
@ -530,7 +532,7 @@
localStorage.removeItem(viewedIssueKey); localStorage.removeItem(viewedIssueKey);
$('.issueContainer').hide(); $('.issueContainer').hide();
$('.scrumContainer').css('padding-left','0px'); $('.scrumContainer').css('padding-left','300px');
} }
// Filtre sur les issues et backup des filtres en localstorage // Filtre sur les issues et backup des filtres en localstorage