From 2750fd0681906683fba5a82d0bfa5d50b6ec010f Mon Sep 17 00:00:00 2001 From: Niek Rabelink Date: Tue, 15 Sep 2026 16:26:00 +0200 Subject: [PATCH] Filter changes --- jira.php | 256 +++++++++++++++++++++++++++++++++++++++++++++------- planner.php | 19 +++- 2 files changed, 239 insertions(+), 36 deletions(-) diff --git a/jira.php b/jira.php index ed02880..03cf8e4 100644 --- a/jira.php +++ b/jira.php @@ -1473,14 +1473,193 @@ function updateProjectPills() { }); } +// Hulpfunctie: Match een issue tegen een JQL clausule +function matchJqlClause(iss, jql) { + if (!jql || typeof jql !== 'string') return true; + const trimmed = jql.trim(); + if (!trimmed) return true; + + const issPKey = getIssueProjectKey(iss); + const issPName = (iss.project || '').toLowerCase().trim(); + const issOl = (iss.onderwerp_logging || '').toLowerCase().trim(); + const issSum = (iss.summary || '').toLowerCase(); + const issPriority = (iss.priority || '').toLowerCase().trim(); + const issType = (iss.issue_type || '').toLowerCase().trim(); + const issStatus = (iss.status || '').toLowerCase().trim(); + + // Normalisatie helper voor onderwerpteksten (verwijder leestekens, spaties, streepjes voor flexibele matching) + function normalizeText(str) { + if (!str) return ''; + return str.toLowerCase().replace(/["']/g, '').replace(/[-_\s]/g, '').trim(); + } + + // Splits condities op AND (hoofdletterongevoelig) + const conditions = trimmed.split(/\s+AND\s+/i); + + for (let rawCond of conditions) { + const cond = rawCond.trim(); + if (!cond) continue; + + // --- 1. Project condities --- + // project = "SD" of project = SD + const projEqMatch = cond.match(/^project\s*=\s*["']?([A-Z0-9_-]+)["']?$/i); + if (projEqMatch) { + const req = projEqMatch[1].toUpperCase().trim(); + if (issPKey !== req && !issPName.includes(req.toLowerCase())) { + return false; + } + continue; + } + + // project != "SD" of project != SD + const projNeqMatch = cond.match(/^project\s*!=\s*["']?([A-Z0-9_-]+)["']?$/i); + if (projNeqMatch) { + const notReq = projNeqMatch[1].toUpperCase().trim(); + if (issPKey === notReq || issPName.includes(notReq.toLowerCase())) { + return false; + } + continue; + } + + // project in ("SD", "IN") + const projInMatch = cond.match(/^project\s+in\s*\(([^)]+)\)$/i); + if (projInMatch) { + const allowed = projInMatch[1].split(',').map(s => s.replace(/["']/g, '').toUpperCase().trim()); + if (!allowed.includes(issPKey) && !allowed.some(p => issPName.includes(p.toLowerCase()))) { + return false; + } + continue; + } + + // project not in ("SD", "IN") + const projNotInMatch = cond.match(/^project\s+not\s+in\s*\(([^)]+)\)$/i); + if (projNotInMatch) { + const disallowed = projNotInMatch[1].split(',').map(s => s.replace(/["']/g, '').toUpperCase().trim()); + if (disallowed.includes(issPKey) || disallowed.some(p => issPName.includes(p.toLowerCase()))) { + return false; + } + continue; + } + + // --- 2. Onderwerp logging condities --- + // bv: "Onderwerp logging[dropdown]" in (Koppelingen, SSO-koppeling, Rest-endpoints, Datadump, UWV-koppeling, Migratie) + const olInMatch = cond.match(/["']?(?:onderwerp(?:[\w\s\[\]_-]*)?|customfield_\d+)["']?\s+in\s*\(([^)]+)\)/i); + if (olInMatch) { + const allowedTopics = olInMatch[1].split(',').map(s => normalizeText(s)).filter(Boolean); + const normOl = normalizeText(issOl); + let matched = false; + if (normOl) { + matched = allowedTopics.some(t => normOl === t || normOl.includes(t) || t.includes(normOl)); + } + if (!matched && !normOl) { + // Als onderwerp_logging op het ticket leeg is, kijk in de summary + const normSum = normalizeText(issSum); + matched = allowedTopics.some(t => normSum.includes(t)); + } + if (!matched) return false; + continue; + } + + // "Onderwerp logging[dropdown]" not in (...) + const olNotInMatch = cond.match(/["']?(?:onderwerp(?:[\w\s\[\]_-]*)?|customfield_\d+)["']?\s+not\s+in\s*\(([^)]+)\)/i); + if (olNotInMatch) { + const forbiddenTopics = olNotInMatch[1].split(',').map(s => normalizeText(s)).filter(Boolean); + const normOl = normalizeText(issOl); + if (normOl && forbiddenTopics.some(t => normOl === t || normOl.includes(t) || t.includes(normOl))) { + return false; + } + continue; + } + + // "Onderwerp logging[dropdown]" = "..." + const olEqMatch = cond.match(/["']?(?:onderwerp(?:[\w\s\[\]_-]*)?|customfield_\d+)["']?\s*=\s*["']?([^"'\)]+)["']?/i); + if (olEqMatch) { + const reqTopic = normalizeText(olEqMatch[1]); + const normOl = normalizeText(issOl); + const matched = (normOl === reqTopic || normOl.includes(reqTopic) || reqTopic.includes(normOl) || (!normOl && normalizeText(issSum).includes(reqTopic))); + if (!matched) return false; + continue; + } + + // "Onderwerp logging[dropdown]" != "..." + const olNeqMatch = cond.match(/["']?(?:onderwerp(?:[\w\s\[\]_-]*)?|customfield_\d+)["']?\s*!=\s*["']?([^"'\)]+)["']?/i); + if (olNeqMatch) { + const notTopic = normalizeText(olNeqMatch[1]); + const normOl = normalizeText(issOl); + if (normOl && (normOl === notTopic || normOl.includes(notTopic))) { + return false; + } + continue; + } + + // --- 3. Prioriteit condities --- + const prioInMatch = cond.match(/^priority\s+in\s*\(([^)]+)\)$/i); + if (prioInMatch) { + const allowed = prioInMatch[1].split(',').map(s => s.replace(/["']/g, '').toLowerCase().trim()); + if (!allowed.includes(issPriority)) return false; + continue; + } + const prioEqMatch = cond.match(/^priority\s*=\s*["']?([^"']+)["']?$/i); + if (prioEqMatch) { + const req = prioEqMatch[1].toLowerCase().trim(); + if (issPriority !== req) return false; + continue; + } + + // --- 4. Type condities --- + const typeInMatch = cond.match(/^(?:issuetype|type)\s+in\s*\(([^)]+)\)$/i); + if (typeInMatch) { + const allowed = typeInMatch[1].split(',').map(s => s.replace(/["']/g, '').toLowerCase().trim()); + if (!allowed.includes(issType)) return false; + continue; + } + const typeEqMatch = cond.match(/^(?:issuetype|type)\s*=\s*["']?([^"']+)["']?$/i); + if (typeEqMatch) { + const req = typeEqMatch[1].toLowerCase().trim(); + if (issType !== req) return false; + continue; + } + + // --- 5. StatusCategory conditie --- + if (/statusCategory\s*!=\s*Done/i.test(cond)) { + if (isDoneStatus(issStatus) || (iss.status_category || '').toLowerCase() === 'done') { + return false; + } + continue; + } + if (/statusCategory\s*=\s*Done/i.test(cond)) { + if (!isDoneStatus(issStatus) && (iss.status_category || '').toLowerCase() !== 'done') { + return false; + } + continue; + } + + // --- 6. Status condities --- + const statusInMatch = cond.match(/^status\s+in\s*\(([^)]+)\)$/i); + if (statusInMatch) { + const allowed = statusInMatch[1].split(',').map(s => s.replace(/["']/g, '').toLowerCase().trim()); + if (!allowed.includes(issStatus)) return false; + continue; + } + const statusEqMatch = cond.match(/^status\s*=\s*["']?([^"']+)["']?$/i); + if (statusEqMatch) { + const req = statusEqMatch[1].toLowerCase().trim(); + if (issStatus !== req) return false; + continue; + } + } + + return true; +} + // Hulpfunctie: Bepaal of een ticket tot een specifieke wachtrij behoort function isIssueInQueue(iss, qCode) { if (!qCode) return true; const code = qCode.toUpperCase().trim(); + const pKey = getIssueProjectKey(iss); + const pName = (iss.project || '').toLowerCase().trim(); const ol = (iss.onderwerp_logging || '').toLowerCase().trim(); const sum = (iss.summary || '').toLowerCase(); - const pKey = (iss.project_key || '').toUpperCase().trim(); - const pName = (iss.project || '').toLowerCase().trim(); // Multi-queue matching voor de ingelogde gebruiker if (code === 'MY_QUEUES') { @@ -1489,38 +1668,51 @@ function isIssueInQueue(iss, qCode) { return myQueues.some(q => isIssueInQueue(iss, q)); } - const dxTerms = ['koppeling', 'sso', 'endpoint', 'rest', 'datadump', 'uwv', 'migratie', 'api', 'webhook', 'integrat']; - const matchesDx = dxTerms.some(t => ol.includes(t) || sum.includes(t)); - - if (code === 'DX') { - return matchesDx; - } - if (code === 'SD') { - return (pKey === 'SD' || pName.includes('servicedesk') || !matchesDx); - } - if (code === 'IN') { - return (pKey === 'IN' || pName.includes('interface') || matchesDx || ol.includes('interface') || sum.includes('interface')); - } - if (code === 'AAN') { - return (pKey === 'AAN' || pName.includes('aanvraag') || ol.includes('aanvraag') || sum.includes('aanvraag')); - } - if (code === 'GEN') { - return (pKey === 'GEN' || pName.includes('general') || pName.includes('algemeen') || ol.includes('general') || ol.includes('algemeen')); - } - - // Aangepaste wachtrijfilter uit de database + // 1. Zoek geconfigureerd wachtrijfilter in jiraData.queue_filters (database) const fObj = (jiraData.queue_filters || []).find(f => f.filter_code && f.filter_code.toUpperCase() === code); if (fObj) { - if (fObj.project_key && pKey && fObj.project_key.toUpperCase() !== pKey) { - return false; + // Project key validatie indien expliciet in filter ingesteld + if (fObj.project_key) { + const filterPKey = fObj.project_key.toUpperCase().trim(); + if (pKey !== filterPKey && !pName.includes(filterPKey.toLowerCase())) { + return false; + } } - const clause = (fObj.jql_clause || '').toLowerCase(); - if (ol && (clause.includes(ol) || ol.includes(code.toLowerCase()))) { - return true; + // JQL clausule evaluatie + if (fObj.jql_clause && fObj.jql_clause.trim()) { + return matchJqlClause(iss, fObj.jql_clause); + } + // Als er alleen een project_key was zonder nadere jql_clause: + if (fObj.project_key) { + const filterPKey = fObj.project_key.toUpperCase().trim(); + return pKey === filterPKey || pName.includes(filterPKey.toLowerCase()); } - if (clause.includes('koppeling') && matchesDx) return true; - if (clause.includes(pKey.toLowerCase())) return true; } + + // 2. Fallbacks voor standaard wachtrijen (als er geen filter in de database staat) + if (code === 'DX') { + const isSd = (pKey === 'SD' || pName.includes('servicedesk')); + if (!isSd) return false; + const dxTerms = ['koppeling', 'sso', 'endpoint', 'rest', 'datadump', 'uwv', 'migratie']; + return dxTerms.some(t => ol.includes(t) || sum.includes(t)); + } + + if (code === 'SD') { + return (pKey === 'SD' || pName.includes('servicedesk')); + } + + if (code === 'IN') { + return (pKey === 'IN' || pName.includes('interface')); + } + + if (code === 'AAN') { + return (pKey === 'AAN' || pName.includes('aanvraag')); + } + + if (code === 'GEN') { + return (pKey === 'GEN' || pName.includes('general') || pName.includes('algemeen')); + } + return true; } @@ -1561,7 +1753,7 @@ function updateProjectCounts() { const el = document.getElementById(`projCount_${key}`); if (el) { const cnt = currentList.filter(iss => { - const pk = (iss.project_key || '').toUpperCase().trim(); + const pk = getIssueProjectKey(iss); const pn = (iss.project || '').toLowerCase().trim(); if (pk === key) return true; if (key === 'SD' && pn.includes('servicedesk')) return true; @@ -1589,7 +1781,7 @@ function updateQueueCounts() { if (currentProject) { const pNorm = currentProject.trim().toUpperCase(); currentList = currentList.filter(iss => { - const pk = (iss.project_key || '').trim().toUpperCase(); + const pk = getIssueProjectKey(iss); const pn = (iss.project || '').trim().toLowerCase(); return pk === pNorm || pn.includes(pNorm.toLowerCase()); }); @@ -1689,7 +1881,7 @@ function applyFilters() { // Filter tickets const filtered = all.filter(iss => { - const issPKey = (iss.project_key || '').toUpperCase().trim(); + const issPKey = getIssueProjectKey(iss); const issPName = (iss.project || '').toLowerCase().trim(); // 1. Status Filter via Checkboxes diff --git a/planner.php b/planner.php index 4eae6eb..0d28023 100644 --- a/planner.php +++ b/planner.php @@ -4671,15 +4671,26 @@ let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': function isIssueInQueue(iss, qCode) { if (!qCode) return true; - const code = qCode.toUpperCase(); + const code = qCode.toUpperCase().trim(); + let pKey = (iss.project_key || '').toUpperCase().trim(); + if (!pKey && iss.key) { + const parts = iss.key.split('-'); + if (parts.length > 0) pKey = parts[0].toUpperCase().trim(); + } + const pName = (iss.project || '').toLowerCase().trim(); + const isSd = (pKey === 'SD' || pName.includes('servicedesk') || !pKey); + const ol = (iss.onderwerp_logging || '').toLowerCase().trim(); const sum = (iss.summary || '').toLowerCase(); - const dxTerms = ['koppeling', 'sso', 'endpoint', 'rest', 'datadump', 'uwv', 'migratie', 'api', 'webhook', 'integrat']; - const matchesDx = dxTerms.some(t => ol.includes(t) || sum.includes(t)); + const dxTerms = ['koppeling', 'sso', 'endpoint', 'rest', 'datadump', 'uwv', 'migratie']; + const matchesDx = isSd && dxTerms.some(t => ol.includes(t) || sum.includes(t)); if (code === 'DX') return matchesDx; - if (code === 'SD') return !matchesDx; + if (code === 'SD') return isSd && !matchesDx; + if (code === 'IN') return (pKey === 'IN' || pName.includes('interface')); + if (code === 'AAN') return (pKey === 'AAN' || pName.includes('aanvraag')); + if (code === 'GEN') return (pKey === 'GEN' || pName.includes('general') || pName.includes('algemeen')); return true; } -- 2.52.0