/
amesk
/
gitlab-analyzer
Обзор
Документация
Войти
/
amesk
/
gitlab-analyzer
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
renderer.js
411 строк
17 KB
amesk
Added basic tooltips.
06 фев 2025, 15:44
06 фев 2025, 15:44
e74c6b7
Код
Авторство
О чём код?
// Ваш класс RegexFilter class RegexFilter { constructor() { this.operators = { 'AND': '&&', 'OR': '||', 'NOT': '!' }; } // Проверяет, является ли токен числом (ID задачи) isNumber(token) { return /^\d+$/.test(token); } // Проверяет, является ли токен оператором isOperator(token) { return Object.keys(this.operators).includes(token.toUpperCase()); } // Токенизация входной строки tokenize(condition) { condition = condition.replace(/RE\("((?:\\["\\]|[^"\\])*)"\)/g, (match, content) => { content = content.replace("(","\r").replace(")","\n"); return content.replace(/\\(["\\])/g, (_, ch) => ch); }); return condition .replace(/(\(|\)|AND|OR|NOT)/gi, ' $1 ') // Добавляем пробелы вокруг операторов .replace("\r","(").replace("\n",")") // Возвращаем подмененные скобки на место .split(/\s+/) .filter(t => t.trim()); } // Преобразует токены в JS-выражение parseTokens(tokens) { const mapped_tokens = tokens.map(token => { const normalizedToken = token.trim(); if (normalizedToken == '(') { return '('; } else if (normalizedToken == ')') { return ')'; } else if (this.isOperator(normalizedToken)) { return this.operators[normalizedToken.toUpperCase()]; } return `this.checkText(issue, '${normalizedToken.replace(/'/g, "\\'")}')`; }); return mapped_tokens.join(' '); } // Проверяет наличие текста в задаче, текст сам по себе может быть regexp'ом checkText(issue, text) { const searchText = text.toLowerCase(); const regex = new RegExp(searchText, 'i'); return ( regex.test(issue.title.toLowerCase()) || issue.labels.some(label => regex.test(label[0].toLowerCase())) || regex.test(issue.iid.toString()) || (issue.assignee && regex.test(issue.assignee.toLowerCase())) || // Фильтрация по Assignee (issue.milestone && regex.test(issue.milestone.toLowerCase())) // Фильтрация по Milestone ); } // Создает функцию фильтрации createFilterFunction(condition) { const tokens = this.tokenize(condition); const jsExpression = this.parseTokens(tokens); try { // Используем Function вместо eval для безопасности return new Function('issue', ` return ${jsExpression}; `).bind(this); } catch (e) { throw new Error("Invalid condition syntax"); } } // Фильтрует список задач filterList(list, condition) { try { const filterFunc = this.createFilterFunction(condition); return list.filter(issue => filterFunc(issue)); } catch (e) { console.error("Filter error:", e.message); return []; } } } //const token = await window.electronAPI.loadToken(); // Остальной JavaScript-код let allIssues = []; let selectedIssues = new Set(); function setLoadingState(isLoading) { const searchInput = document.getElementById('searchInput'); const loadIssuesButton = document.getElementById('loadIssues'); const loadingSpinner = document.getElementById('loadingSpinner'); if (isLoading) { searchInput.disabled = true; loadIssuesButton.disabled = true; loadingSpinner.style.display = 'block'; } else { searchInput.disabled = false; loadIssuesButton.disabled = false; loadingSpinner.style.display = 'none'; } } function updateFormState() { const issueIdField = document.getElementById('issueId'); const labelField = document.getElementById('label'); const actionField = document.getElementById('action'); const modifyButton = document.getElementById('labelForm').querySelector('button[type="submit"]'); if (selectedIssues.size > 1) { issueIdField.value = ''; issueIdField.disabled = true; } else if (selectedIssues.size === 1) { issueIdField.value = Array.from(selectedIssues)[0]; issueIdField.disabled = false; } else { issueIdField.disabled = false; } // Разблокируем поля Label и Action, если есть выбранные задачи или введен Issue ID const isFormEnabled = selectedIssues.size > 0 || issueIdField.value.trim() !== ''; labelField.disabled = !isFormEnabled; actionField.disabled = !isFormEnabled; modifyButton.disabled = !isFormEnabled; } function scrollToIssue(issueId) { const issueRow = document.querySelector(`tr[data-issue-id="${issueId}"]`); if (issueRow) { issueRow.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } async function loadIssues() { const projectIdInput = document.getElementById('projectId'); projectId = projectIdInput.value; if (!projectId) { projectId = "navigation/uninav"; projectIdInput.value = "navigation/uninav"; projectIdInput.classList.remove('is-invalid'); } setLoadingState(true); try { const result = await window.electronAPI.getOpenIssues(projectId); if (result.success) { allIssues = result.issues; renderIssues(allIssues); updateLabelList(); // Инициализация списка меток } else { projectIdInput.classList.add('is-invalid'); } } catch (error) { console.error('Error loading issues:', error); projectIdInput.classList.add('is-invalid'); } finally { setLoadingState(false); } } function updateLabelList(filterText = '') { const labelList = document.getElementById('labelList'); labelList.innerHTML = ''; // Очищаем текущий список // Собираем все уникальные метки из задач const uniqueLabels = new Set(); allIssues.forEach(issue => { issue.labels.forEach(label => { uniqueLabels.add(label[0]); // Добавляем имя метки }); }); // Фильтруем метки по введенному тексту const filteredLabels = Array.from(uniqueLabels).filter(label => label.toLowerCase().includes(filterText.toLowerCase()) ); // Добавляем отфильтрованные метки в datalist filteredLabels.forEach(label => { const option = document.createElement('option'); option.value = label; labelList.appendChild(option); }); } document.getElementById('loadIssues').addEventListener('click', loadIssues); document.getElementById('projectId').addEventListener('keydown', function(event) { if (event.key === 'Enter') { event.preventDefault(); // Предотвращаем стандартное поведение формы loadIssues(); } }); function formatTime(seconds) { const secondsInHour = 3600; const secondsInDay = 8 * secondsInHour; // 8 часов const secondsInWeek = 5 * secondsInDay; // 5 дней в неделю // Вычисляем количество недель, дней и часов let weeks = Math.floor(seconds / secondsInWeek); let remaining = seconds % secondsInWeek; let days = Math.floor(remaining / secondsInDay); remaining = remaining % secondsInDay; let hours = Math.floor(remaining / secondsInHour); // Формируем строку, исключая нулевые компоненты let result = []; if (weeks > 0) result.push(`${weeks}w`); if (days > 0) result.push(`${days}d`); if (hours > 0) result.push(`${hours}h`); // Объединяем компоненты в строку return result.join(' '); } function renderIssues(issues) { const issuesList = document.getElementById('issues'); issuesList.innerHTML = ''; issues.forEach(issue => { const row = document.createElement('tr'); row.setAttribute('data-issue-id', issue.iid); let spent_time = undefined; let estimated_time = undefined; if (issue.total_time_spent) { spent_time = formatTime(issue.total_time_spent); } if (issue.estimation) { estimated_time = formatTime(issue.estimation); } row.innerHTML = ` <td> <input type="checkbox" class="issue-checkbox" value="${issue.title}"> </td> <th scope="row">${issue.iid}</th> <td><a href="${issue.url}">${issue.title}</a></td> <td> ${issue.labels.map(label => `<span class="badge" style="background-color: ${label[1]};">${label[0]}</span>`).join(' ')} </td> <td>${issue.assignee || ''}</td> <td>${issue.milestone || ''}</td> <td>${estimated_time || ''}</td> <td>${spent_time || ''}</td> `; const checkbox = row.querySelector('.issue-checkbox'); checkbox.addEventListener('change', (e) => { if (e.target.checked) { selectedIssues.add(issue.iid); } else { selectedIssues.delete(issue.iid); } updateFormState(); }); row.addEventListener('click', (e) => { if (selectedIssues.size <= 1 && !e.target.classList.contains('issue-checkbox')) { document.getElementById('issueId').value = issue.iid; updateFormState(); } }); issuesList.appendChild(row); }); // Обновляем состояние "Выбрать все" document.getElementById('selectAllCheckbox').checked = false; } document.getElementById('selectAllCheckbox').addEventListener('change', function() { const checkboxes = document.querySelectorAll('.issue-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = this.checked; if (this.checked) { selectedIssues.add(checkbox.value); } else { selectedIssues.delete(checkbox.value); } }); updateFormState(); }); document.getElementById('searchInput').addEventListener('input', function() { const searchTerm = this.value.trim(); // Получаем значение из поля поиска const searchInput = this; if (!searchTerm) { // Если поле поиска пустое, показываем все задачи renderIssues(allIssues); updateLabelList(allIssues); return; } try { // Создаем экземпляр RegexFilter const regexFilter = new RegexFilter(); // Фильтруем задачи с помощью RegexFilter const filteredIssues = regexFilter.filterList(allIssues, searchTerm); // Отображаем отфильтрованные задачи renderIssues(filteredIssues); updateLabelList(allIssues); } catch (e) { // Если произошла ошибка, показываем сообщение об ошибке console.log(e) const errorDiv = document.createElement('div'); errorDiv.className = 'alert alert-danger'; errorDiv.textContent = 'Invalid condition. Please check your input.'; document.body.appendChild(errorDiv); setTimeout(() => errorDiv.remove(), 3000); setTimeout(() => { searchInput.focus(); const length = searchInput.value.length; searchInput.setSelectionRange(length, length); }, 50); } }); document.getElementById('labelForm').addEventListener('submit', async function(event) { event.preventDefault(); const projectId = document.getElementById('projectId').value; const issueIds = selectedIssues.size > 0 ? Array.from(selectedIssues) : [document.getElementById('issueId').value]; const label = document.getElementById('label').value; const action = document.getElementById('action').value; if (!issueIds.length || !issueIds[0]) { alert('Please select at least one issue or enter an issue ID.'); return; } try { const results = await Promise.all( issueIds.map(issueId => window.electronAPI.updateLabel(projectId, issueId, label, action) ) ); if (results.every(result => result.success)) { const successDiv = document.createElement('div'); successDiv.className = 'alert alert-success'; successDiv.textContent = 'Labels updated successfully'; document.body.appendChild(successDiv); setTimeout(() => successDiv.remove(), 2000); } else { const errorDiv = document.createElement('div'); errorDiv.className = 'alert alert-danger'; errorDiv.textContent = 'Error: ' + results.find(result => !result.success).error; document.body.appendChild(errorDiv); setTimeout(() => errorDiv.remove(), 3000); } } catch (error) { console.error('Error:', error); } }); document.getElementById('issueId').addEventListener('input', function() { const issueId = this.value; if (issueId) { scrollToIssue(issueId); } updateFormState(); }); document.getElementById('projectId').addEventListener('input', function() { this.classList.remove('is-invalid'); }); document.getElementById('searchInput').addEventListener('focus', function() { this.classList.remove('is-invalid'); }); document.getElementById('helpButton').addEventListener('click', () => { window.electronAPI.openHelpWindow(); }); updateFormState(); document.addEventListener('click', (event) => { const link = event.target.closest('a[href^="http"]'); if (link) { event.preventDefault(); const url = link.getAttribute('href'); window.electronAPI.openExternalLink(url); } }); document.getElementById('openGitLab').addEventListener('click', () => { console.log('openGitLab'); const projectId = document.getElementById('projectId').value; const gitlabUrl = `${window.electronAPI.GITLAB_URL}/${projectId}/-/issues`; window.electronAPI.openExternalLink(gitlabUrl); }); document.getElementById('label').addEventListener('input', function (event) { const filterText = event.target.value; // Получаем введенный текст updateLabelList(filterText); // Обновляем список меток }); window.electronAPI.onTokenLoaded(async (event, data) => { console.log(`Got API key: ${data.message}`); token = data.message; const modal = document.getElementById('apiKeyModal'); if (!token) { const apiKeyInput = document.getElementById('apiKeyInput'); const submitButton = document.getElementById('submitApiKey'); // Показываем модальное окно modal.style.display = 'flex'; // Обработка нажатия кнопки Submit submitButton.addEventListener('click', () => { const apiKey = apiKeyInput.value.trim(); if (apiKey) { // Отправляем API key в главный процесс window.electronAPI.saveToken(apiKey); // Скрываем модальное окно modal.style.display = 'none'; } else { alert('Please enter a valid API Key.'); } }); } else { modal.style.display = 'none'; } //alert(data.message); }); document.getElementById('logoutButton').addEventListener('click', async () => { try { const result = await window.electronAPI.logout(); if (result.success) { // Показываем модальное окно для ввода нового токена const modal = document.getElementById('apiKeyModal'); modal.style.display = 'flex'; } else { alert('Logout failed: ' + result.error); } } catch (error) { console.error('Error during logout:', error); } }); window.addEventListener('DOMContentLoaded', () => { console.log('DOMContentLoaded'); const token = window.electronAPI.loadToken(); // Инициализация tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); });