-
Cedric Eyssette a rédigéCedric Eyssette a rédigé
script.js 12,26 Kio
// Ce script permet d'afficher un tableau de tickets et de lancer l'ouverture de nouveaux tickets sur la ForgeEdu.
// ID du projet
const idProjet = 3318;
// URL de l'API
const apiUrl = `https://forge.apps.education.fr/api/v4/projects/${idProjet}/issues`;
// Préfixe appli pour le localStorage
const prefixeAppli = 'laboiteareves';
// Quelle heure est-il ?
const now = new Date();
const inputDescriptions = document.getElementById("inputDescriptions");
const tableBody = document.querySelector("#issuesTable tbody");
const etatTri = {
cle: null, // La clé actuellement triée
croissant: true // Par défaut, tri croissant
};
// Labels
const typesLabels = [
{
nom: 'help',
description : "appel à contributions sur un projet",
ticket : "Sur l'application ... j'aurais besoin de ...",
couleur : 'rgb(204, 51, 139)'
},
{
nom: 'idée',
description : "proposition de nouvelle application",
ticket : "J'ai un rêve d'application ...",
couleur : 'rgb(0, 153, 102)'
},
{
nom: 'bug',
description : "signalement de bug",
ticket : "J'ai constaté que...",
couleur : '#dc143c'
},
{
nom: 'épinglé',
description : "pour afficher en haut",
ticket : null,
couleur : '#9400d3'
}
];
let labelsActifs = ['help','idée'];
const tousLesSpanLabels = document.querySelectorAll('#choixLabels span');
//Lancement de l'appli
async function lancementAppli() {
await verifierLocalStorage();
preparerlesInputLabel();
fetchIssues();
toggleDescriptions();
trierTableau(etatTri.cle);
}
lancementAppli();
async function verifierLocalStorage() {
let valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'labelsActifs');
if (valeurARecuperer) {
labelsActifs = Array.from(valeurARecuperer.split(','));
}
valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'descriptions');
inputDescriptions.checked = valeurARecuperer != 'fase';
valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'trierPar');
etatTri.cle = valeurARecuperer || 'date';
valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'triCroissant');
etatTri.croissant = valeurARecuperer != 'false';
}
function preparerlesInputLabel() {
tousLesSpanLabels.forEach(span => {
// Récupérer la case à cocher
const caseACocher = span.querySelector('input');
// Cocher la case
if (labelsActifs.includes(caseACocher.value)) {
caseACocher.checked=true;
}
// Affecter la bonne couleur
let couleur = typesLabels.find(item => item.nom === caseACocher.value).couleur;
span.style.backgroundColor = couleur;
});
}
// Fonction pour récupérer les tickets
async function fetchIssues() {
try {
// Récupération des tickets
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error("Erreur lors de la récupération des données.");
}
let issues = await response.json();
issues = issues
.filter(issue => issue.state !== 'closed') // Suppression des issues fermées
.filter(issue => !issue.labels.some(label => label.toLowerCase() === 'bug')) // Suppression des issues avec label "bug"
.sort((a, b) => {
// Priorité aux issues avec 'épinglé' dans leur label
const aPinned = a.labels.some(label => label.toLowerCase().includes('épinglé'));
const bPinned = b.labels.some(label => label.toLowerCase().includes('épinglé'));
if (aPinned && !bPinned) return -1; // 'a' doit être avant 'b'
if (!aPinned && bPinned) return 1; // 'b' doit être avant 'a'
// Sinon, tri par dernière réponse
return new Date(b.updated_at) - new Date(a.updated_at);
});
// Affichage des tickets
displayIssues(issues);
} catch (error) {
console.error("Erreur:", error);
tableBody.innerHTML = `<tr><td colspan="4">Impossible de charger les tickets.</td></tr>`;
}
}
// Fonction pour afficher les tickets
function displayIssues(issues) {
tableBody.innerHTML = ""; // Vider le tableau
issues.forEach(issue => {
console.log(issue.user_notes_count)
let description = issue.description;
// Limiter à 200 caractères la description
if (description.length > 200) {
description = `${description.slice(0, 200)} ...`;
// Découper les 20 derniers caractères et construire la chaîne avec styles
if (description.length > 50) {
const mainText = description.slice(0, -50); // Texte principal sans les 20 derniers
const fadingText = description.slice(-50); // Les 20 derniers caractères
description = `
<span class="text-main">${mainText}</span>
<span class="text-fade">${fadingText}</span>
`;
}
}
// Récupération du label
const label = issue.labels[0] || '';
if (label) {
// Trouver l'objet correspondant dans le tableau typesLabels
let type = typesLabels.find(item => item.nom === label);
// Récupérer la couleur ou définir une valeur par défaut si non trouvée
let couleur = type ? type.couleur : 'yellow';
spanLabel = `<span class="label" style="background-color:${couleur}">${label}</span>`;
} else {
spanLabel = '';
}
console.log(`
------------ TICKET ------------
${issue.title || 'Pas de titre'}
${issue.state || 'Statut indéfini'}
${label || 'Pas de label'}
--------------------------------
`)
let date = new Date(issue.updated_at);
const avatar = issue.author.avatar_url ? `<img class="avatar" src="${issue.author.avatar_url}">` : "👤";
// Création d'une ligne dans le tableau
const row = document.createElement("tr");
row.innerHTML = `
<td>${spanLabel}<a class="titre" href="${issue.web_url}" target="_blank">${issue.title}</a>
<div class="description">
${description}
<a class="bouton" href="${issue.web_url}" target="_blank">... la suite</a>
</div>
</td>
<td class="nombre">${issue.user_notes_count} 🗨️</td>
<td class="nombre">${issue.upvotes} 👍</td>
<td class="auteur">${avatar} <a target="_blank" href="${issue.author.web_url}">${issue.author.name}</a></td>
<td>${formatDate(date)}</td>
`;
if (label) {
row.label = label;
if (label!='épinglé' && !labelsActifs.includes(label)) {
row.classList.add('hide');
}
}
row.auteur = issue.author.name;
row.titre = issue.title;
row.reponses = issue.user_notes_count;
row.date = date;
row.votes = issue.upvotes;
tableBody.appendChild(row);
});
}
// Pour afficher la date
function formatDate(date) {
const diffMs = now - date; // Différence en millisecondes
const diffMinutes = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / (3600 * 1000));
// Vérifications
if (diffMinutes < 5) {
return "à l'instant";
} else if (diffHours < 2) {
return "- de 2H";
} else if (date.toDateString() === now.toDateString()) {
return "aujourd'hui";
} else {
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
if (date.toDateString() === yesterday.toDateString()) {
return "Hier";
}
}
// Date par défaut
return date.toLocaleDateString();
}
// Fonction pour ouvrir une fenêtre Gitlab de nouveau ticket
function ajouterTicket(label) {
console.log('ajout de ticket',label);
const url = 'https://forge.apps.education.fr/laboiteareves/laboiteareves.forge.apps.education.fr/-/issues/new'; // URL de création de ticket
// Trouver l'objet correspondant dans le tableau typesLabels
let type = typesLabels.find(item => item.nom === label);
// Récupérer le motif ticket à utiliser
let motif = type.ticket;
const description = `/label ${label} <!-- Merci de ne pas supprimer cette ligne, qui permet de bien catégoriser votre proposition -->\n\n${motif} `;
// Définir les paramètres pour pré-remplir le ticket
const params = new URLSearchParams();
//params.set('issue[title]', 'Nouveau ticket');
params.set('issue[description]', description);
const fullUrl = `${url}?${params.toString()}`;
window.open(fullUrl, "_self");
}
// Fonction pour mettre à jour les labels actifs
function majLabelsActifs(){
// On vide le tableau
labelsActifs = [];
// On le remplit
tousLesSpanLabels.forEach(span => {
// Récupérer la case à cocher
const caseACocher = span.querySelector('input');
// Ajouter le label au tableau
if (caseACocher.checked) {
labelsActifs.push(caseACocher.value);
}
});
filtreTickets();
localStorage.setItem(prefixeAppli+'-'+'labelsActifs',labelsActifs.join(','));
}
// Fonction pour filtrer les tickets
function filtreTickets() {
// On récupère les lignes
const lignesDuTableau = document.querySelectorAll('#issuesTable tbody tr');
lignesDuTableau.forEach(ligne => {
// On vérifie si le label est dans le tableau des labels actifs
const estActif = labelsActifs.includes(ligne.label) || ligne.label === 'épinglé';
// On met à jour la classe hide selon
if (estActif) {
ligne.classList.remove('hide');
} else {
ligne.classList.add('hide');
}
});
}
function trierTableau(cle) {
console.log('Tri par', cle);
let entete = document.getElementById(cle);
const entetes = document.querySelectorAll('#issuesTable th');
entetes.forEach(enteteCourante => {
enteteCourante.classList.remove('actif');
});
entete.classList.add('actif');
const tableau = document.getElementById('issuesTable');
const tbody = tableau.querySelector('tbody');
const lignes = Array.from(tbody.querySelectorAll('tr'));
// Déterminer le sens du tri
if (etatTri.cle === cle) {
// Inverser le sens si la même colonne est sélectionnée
etatTri.croissant = !etatTri.croissant;
} else {
// Sinon, on trie par cette clé en croissant
etatTri.cle = cle;
etatTri.croissant = true;
}
localStorage.setItem(prefixeAppli+'-'+'trierPar',etatTri.cle);
localStorage.setItem(prefixeAppli+'-'+'triCroissant',etatTri.croissant);
// Trier les lignes en fonction du label 'épinglé' et des valeurs
lignes.sort((a, b) => {
// Vérifier si l'un des éléments est "épinglé"
if (a.label === 'épinglé' && b.label !== 'épinglé') return -1;
if (a.label !== 'épinglé' && b.label === 'épinglé') return 1;
// Sinon, on effectue le tri habituel
const valeurA = a[cle];
const valeurB = b[cle];
let comparaison = 0;
if (valeurA instanceof Date && valeurB instanceof Date) {
comparaison = valeurA - valeurB; // Tri par date
} else if (typeof valeurA === 'number' && typeof valeurB === 'number') {
comparaison = valeurA - valeurB; // Tri par nombre
} else {
comparaison = valeurA.localeCompare(valeurB, undefined, { sensitivity: 'base' }); // Tri par chaîne
}
// Inverser l'ordre si tri décroissant
return etatTri.croissant ? comparaison : -comparaison;
});
// Réinsérer les lignes triées dans le tableau
lignes.forEach(ligne => tbody.appendChild(ligne));
console.log(`Tri effectué par '${cle}' en ordre ${etatTri.croissant ? 'croissant' : 'décroissant'}`);
}
function toggleDescriptions(){
if (inputDescriptions.checked){
tableBody.classList.remove('nodescriptions');
} else {
tableBody.classList.add('nodescriptions');
}
localStorage.setItem(prefixeAppli+'-'+'descriptions',inputDescriptions.checked);
}